VirtualBox

source: vbox/trunk/src/VBox/Devices/Audio/AudioTestServiceClient.cpp@ 91206

Last change on this file since 91206 was 91042, checked in by vboxsync, 3 years ago

Audio/Validation Kit: Got rid of the embedded (and probably wrong) failure reply struct and replaced it with ATSPKTREPFAIL so that clients also can interpret it more easily. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.6 KB
Line 
1/* $Id: AudioTestServiceClient.cpp 91042 2021-08-31 18:34:40Z vboxsync $ */
2/** @file
3 * AudioTestServiceClient - Audio Test Service (ATS), Client helpers.
4 *
5 * Note: Only does TCP/IP as transport layer for now.
6 */
7
8/*
9 * Copyright (C) 2021 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20
21/*********************************************************************************************************************************
22* Header Files *
23*********************************************************************************************************************************/
24#define LOG_GROUP LOG_GROUP_AUDIO_TEST
25
26#include <iprt/crc.h>
27#include <iprt/err.h>
28#include <iprt/file.h>
29#include <iprt/mem.h>
30#include <iprt/string.h>
31#include <iprt/tcp.h>
32
33#include <VBox/log.h>
34
35#include "AudioTestService.h"
36#include "AudioTestServiceInternal.h"
37#include "AudioTestServiceClient.h"
38
39/** @todo Use common defines between server protocol and this client. */
40
41/**
42 * A generic ATS reply, used by the client
43 * to process the incoming packets.
44 */
45typedef struct ATSSRVREPLY
46{
47 char szOp[ATSPKT_OPCODE_MAX_LEN];
48 /** Pointer to payload data.
49 * This does *not* include the header! */
50 void *pvPayload;
51 /** Size (in bytes) of the payload data.
52 * This does *not* include the header! */
53 size_t cbPayload;
54} ATSSRVREPLY;
55/** Pointer to a generic ATS reply. */
56typedef struct ATSSRVREPLY *PATSSRVREPLY;
57
58
59/*********************************************************************************************************************************
60* Prototypes *
61*********************************************************************************************************************************/
62static int audioTestSvcClientCloseInternal(PATSCLIENT pClient);
63
64/**
65 * Initializes an ATS client, internal version.
66 *
67 * @param pClient Client to initialize.
68 */
69static void audioTestSvcClientInit(PATSCLIENT pClient)
70{
71 RT_BZERO(pClient, sizeof(ATSCLIENT));
72}
73
74/**
75 * Destroys an ATS server reply.
76 *
77 * @param pReply Reply to destroy.
78 */
79static void audioTestSvcClientReplyDestroy(PATSSRVREPLY pReply)
80{
81 if (!pReply)
82 return;
83
84 if (pReply->pvPayload)
85 {
86 Assert(pReply->cbPayload);
87 RTMemFree(pReply->pvPayload);
88 pReply->pvPayload = NULL;
89 }
90
91 pReply->cbPayload = 0;
92}
93
94/**
95 * Receives a reply from an ATS server.
96 *
97 * @returns VBox status code.
98 * @param pClient Client to receive reply for.
99 * @param pReply Where to store the reply.
100 * The reply must be destroyed with audioTestSvcClientReplyDestroy() then.
101 * @param fNoDataOk If it's okay that the reply is not expected to have any payload.
102 */
103static int audioTestSvcClientRecvReply(PATSCLIENT pClient, PATSSRVREPLY pReply, bool fNoDataOk)
104{
105 LogFlowFuncEnter();
106
107 PATSPKTHDR pPktHdr;
108 int rc = pClient->pTransport->pfnRecvPkt(pClient->pTransportInst, pClient->pTransportClient, &pPktHdr);
109 if (RT_SUCCESS(rc))
110 {
111 AssertReleaseMsgReturn(pPktHdr->cb >= sizeof(ATSPKTHDR),
112 ("audioTestSvcClientRecvReply: Received invalid packet size (%RU32)\n", pPktHdr->cb),
113 VERR_NET_PROTOCOL_ERROR);
114 pReply->cbPayload = pPktHdr->cb - sizeof(ATSPKTHDR);
115 Log3Func(("szOp=%.8s, cb=%RU32\n", pPktHdr->achOpcode, pPktHdr->cb));
116 if (pReply->cbPayload)
117 {
118 pReply->pvPayload = RTMemDup((uint8_t *)pPktHdr + sizeof(ATSPKTHDR), pReply->cbPayload);
119 }
120 else
121 pReply->pvPayload = NULL;
122
123 if ( !pReply->cbPayload
124 && !fNoDataOk)
125 {
126 LogRelFunc(("Payload is empty (%zu), but caller expected data\n", pReply->cbPayload));
127 rc = VERR_NET_PROTOCOL_ERROR;
128 }
129 else
130 {
131 memcpy(&pReply->szOp, &pPktHdr->achOpcode, sizeof(pReply->szOp));
132 }
133
134 RTMemFree(pPktHdr);
135 pPktHdr = NULL;
136 }
137
138 if (RT_FAILURE(rc))
139 LogRelFunc(("Receiving reply from server failed with %Rrc\n", rc));
140
141 LogFlowFuncLeaveRC(rc);
142 return rc;
143}
144
145/**
146 * Receives a reply for an ATS server and checks if it is an acknowledge (success) one.
147 *
148 * @returns VBox status code.
149 * @retval VERR_NET_PROTOCOL_ERROR if the reply indicates a failure.
150 * @param pClient Client to receive reply for.
151 */
152static int audioTestSvcClientRecvAck(PATSCLIENT pClient)
153{
154 ATSSRVREPLY Reply;
155 RT_ZERO(Reply);
156
157 int rc = audioTestSvcClientRecvReply(pClient, &Reply, true /* fNoDataOk */);
158 if (RT_SUCCESS(rc))
159 {
160 /* Most likely cases first. */
161 if ( RTStrNCmp(Reply.szOp, "ACK ", ATSPKT_OPCODE_MAX_LEN) == 0)
162 {
163 /* Nothing to do here. */
164 }
165 else if (RTStrNCmp(Reply.szOp, "FAILED ", ATSPKT_OPCODE_MAX_LEN) == 0)
166 {
167 LogRelFunc(("Received error from server (cbPayload=%zu)\n", Reply.cbPayload));
168
169 if (Reply.cbPayload)
170 {
171 if ( Reply.cbPayload >= sizeof(int) /* At least the rc must be present. */
172 && Reply.cbPayload <= sizeof(ATSPKTREPFAIL) - sizeof(ATSPKTHDR))
173 {
174 rc = *(int *)Reply.pvPayload; /* Reach error code back to caller. */
175
176 const char *pcszMsg = (char *)Reply.pvPayload + sizeof(int);
177 /** @todo Check NULL termination of pcszMsg? */
178
179 LogRelFunc(("Error message: %s (%Rrc)\n", pcszMsg, rc));
180 }
181 else
182 {
183 LogRelFunc(("Received invalid failure payload (cb=%zu)\n", Reply.cbPayload));
184 rc = VERR_NET_PROTOCOL_ERROR;
185 }
186 }
187 }
188 else
189 {
190 LogRelFunc(("Received invalid opcode ('%.8s')\n", Reply.szOp));
191 rc = VERR_NET_PROTOCOL_ERROR;
192 }
193
194 audioTestSvcClientReplyDestroy(&Reply);
195 }
196
197 LogRelFlowFuncLeaveRC(rc);
198 return rc;
199}
200
201/**
202 * Sends a message plus optional payload to an ATS server.
203 *
204 * @returns VBox status code.
205 * @param pClient Client to send message for.
206 * @param pvHdr Pointer to header data to send.
207 * @param cbHdr Size (in bytes) of \a pvHdr to send.
208 */
209static int audioTestSvcClientSendMsg(PATSCLIENT pClient, void *pvHdr, size_t cbHdr)
210{
211 RT_NOREF(cbHdr);
212 AssertPtrReturn(pClient->pTransport, VERR_INVALID_POINTER);
213 AssertPtrReturn(pClient->pTransportInst, VERR_INVALID_POINTER);
214 AssertPtrReturn(pClient->pTransportClient, VERR_INVALID_POINTER);
215 return pClient->pTransport->pfnSendPkt(pClient->pTransportInst, pClient->pTransportClient, (PCATSPKTHDR)pvHdr);
216}
217
218/**
219 * Initializes a client request header.
220 *
221 * @returns VBox status code.
222 * @param pReqHdr Request header to initialize.
223 * @param cbReq Size (in bytes) the request will have (does *not* include payload).
224 * @param pszOp Operation to perform with the request.
225 * @param cbPayload Size (in bytes) of payload that will follow the header. Optional and can be 0.
226 */
227DECLINLINE (void) audioTestSvcClientReqHdrInit(PATSPKTHDR pReqHdr, size_t cbReq, const char *pszOp, size_t cbPayload)
228{
229 AssertReturnVoid(strlen(pszOp) >= 2);
230 AssertReturnVoid(strlen(pszOp) <= ATSPKT_OPCODE_MAX_LEN);
231
232 /** @todo Validate opcode. */
233
234 RT_BZERO(pReqHdr, sizeof(ATSPKTHDR));
235
236 memcpy(pReqHdr->achOpcode, pszOp, strlen(pszOp));
237 pReqHdr->uCrc32 = 0; /** @todo Do CRC-32 calculation. */
238 pReqHdr->cb = (uint32_t)cbReq + (uint32_t)cbPayload;
239
240 Assert(pReqHdr->cb <= ATSPKT_MAX_SIZE);
241}
242
243/**
244 * Sends an acknowledege response back to the server.
245 *
246 * @returns VBox status code.
247 * @param pClient Client to send command for.
248 */
249static int audioTestSvcClientSendAck(PATSCLIENT pClient)
250{
251 ATSPKTHDR Req;
252 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), "ACK ", 0);
253
254 return audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
255}
256
257/**
258 * Sends a greeting command (handshake) to an ATS server.
259 *
260 * @returns VBox status code.
261 * @param pClient Client to send command for.
262 */
263static int audioTestSvcClientDoGreet(PATSCLIENT pClient)
264{
265 ATSPKTREQHOWDY Req;
266 Req.uVersion = ATS_PROTOCOL_VS;
267 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_HOWDY, 0);
268 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
269 if (RT_SUCCESS(rc))
270 rc = audioTestSvcClientRecvAck(pClient);
271 return rc;
272}
273
274/**
275 * Tells the ATS server that we want to disconnect.
276 *
277 * @returns VBox status code.
278 * @param pClient Client to disconnect.
279 */
280static int audioTestSvcClientDoBye(PATSCLIENT pClient)
281{
282 ATSPKTHDR Req;
283 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), ATSPKT_OPCODE_BYE, 0);
284 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
285 if (RT_SUCCESS(rc))
286 rc = audioTestSvcClientRecvAck(pClient);
287 return rc;
288}
289
290/**
291 * Creates an ATS client.
292 *
293 * @returns VBox status code.
294 * @param pClient Client to create.
295 */
296int AudioTestSvcClientCreate(PATSCLIENT pClient)
297{
298 audioTestSvcClientInit(pClient);
299
300 /*
301 * The default transporter is the first one.
302 */
303 pClient->pTransport = g_apTransports[0]; /** @todo Make this dynamic. */
304
305 return pClient->pTransport->pfnCreate(&pClient->pTransportInst);
306}
307
308/**
309 * Destroys an ATS client.
310 *
311 * @returns VBox status code.
312 * @param pClient Client to destroy.
313 */
314void AudioTestSvcClientDestroy(PATSCLIENT pClient)
315{
316 if (!pClient)
317 return;
318
319 /* ignore rc */ audioTestSvcClientCloseInternal(pClient);
320
321 if (pClient->pTransport)
322 {
323 pClient->pTransport->pfnTerm(pClient->pTransportInst);
324 pClient->pTransport->pfnDestroy(pClient->pTransportInst);
325 pClient->pTransport = NULL;
326 }
327}
328
329/**
330 * Handles a command line option.
331 *
332 * @returns VBox status code.
333 * @param pClient Client to handle option for.
334 * @param ch Option (short) to handle.
335 * @param pVal Option union to store the result in on success.
336 */
337int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal)
338{
339 AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER); /* Must be created first via AudioTestSvcClientCreate(). */
340 if (!pClient->pTransport->pfnOption)
341 return VERR_GETOPT_UNKNOWN_OPTION;
342 return pClient->pTransport->pfnOption(pClient->pTransportInst, ch, pVal);
343}
344
345/**
346 * Connects to an ATS peer, extended version.
347 *
348 * @returns VBox status code.
349 * @param pClient Client to connect.
350 * @param msTimeout Timeout (in ms) waiting for a connection to be established.
351 * Use RT_INDEFINITE_WAIT to wait indefinitely.
352 */
353int AudioTestSvcClientConnectEx(PATSCLIENT pClient, RTMSINTERVAL msTimeout)
354{
355 if (pClient->pTransportClient)
356 return VERR_NET_ALREADY_CONNECTED;
357
358 int rc = pClient->pTransport->pfnStart(pClient->pTransportInst);
359 if (RT_SUCCESS(rc))
360 {
361 rc = pClient->pTransport->pfnWaitForConnect(pClient->pTransportInst,
362 msTimeout, NULL /* pfFromServer */, &pClient->pTransportClient);
363 if (RT_SUCCESS(rc))
364 {
365 rc = audioTestSvcClientDoGreet(pClient);
366 }
367 }
368
369 if (RT_FAILURE(rc))
370 LogRelFunc(("Connecting to server (%RU32ms timeout) failed with %Rrc\n", msTimeout, rc));
371
372 return rc;
373}
374
375/**
376 * Connects to an ATS peer.
377 *
378 * @returns VBox status code.
379 * @param pClient Client to connect.
380 */
381int AudioTestSvcClientConnect(PATSCLIENT pClient)
382{
383 return AudioTestSvcClientConnectEx(pClient, 30 * 1000 /* msTimeout */);
384}
385
386/**
387 * Tells the server to begin a new test set.
388 *
389 * @returns VBox status code.
390 * @param pClient Client to issue command for.
391 * @param pszTag Tag to use for the test set to begin.
392 */
393int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
394{
395 ATSPKTREQTSETBEG Req;
396
397 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
398 AssertRCReturn(rc, rc);
399
400 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
401
402 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
403 if (RT_SUCCESS(rc))
404 rc = audioTestSvcClientRecvAck(pClient);
405
406 return rc;
407}
408
409/**
410 * Tells the server to end a runing test set.
411 *
412 * @returns VBox status code.
413 * @param pClient Client to issue command for.
414 * @param pszTag Tag of test set to end.
415 */
416int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
417{
418 ATSPKTREQTSETEND Req;
419
420 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
421 AssertRCReturn(rc, rc);
422
423 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
424
425 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
426 if (RT_SUCCESS(rc))
427 rc = audioTestSvcClientRecvAck(pClient);
428
429 return rc;
430}
431
432/**
433 * Tells the server to play a (test) tone.
434 *
435 * @returns VBox status code.
436 * @param pClient Client to issue command for.
437 * @param pToneParms Tone parameters to use.
438 * @note How (and if) the server plays a tone depends on the actual implementation side.
439 */
440int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
441{
442 ATSPKTREQTONEPLAY Req;
443
444 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
445
446 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
447
448 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
449 if (RT_SUCCESS(rc))
450 rc = audioTestSvcClientRecvAck(pClient);
451
452 return rc;
453}
454
455/**
456 * Tells the server to record a (test) tone.
457 *
458 * @returns VBox status code.
459 * @param pClient Client to issue command for.
460 * @param pToneParms Tone parameters to use.
461 * @note How (and if) the server plays a tone depends on the actual implementation side.
462 */
463int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
464{
465 ATSPKTREQTONEREC Req;
466
467 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
468
469 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
470
471 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
472 if (RT_SUCCESS(rc))
473 rc = audioTestSvcClientRecvAck(pClient);
474
475 return rc;
476}
477
478/**
479 * Tells the server to send (download) a (packed up) test set archive.
480 * The test set must not be running / open anymore.
481 *
482 * @returns VBox status code.
483 * @param pClient Client to issue command for.
484 * @param pszTag Tag of test set to send.
485 * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
486 */
487int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
488{
489 ATSPKTREQTSETSND Req;
490
491 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
492 AssertRCReturn(rc, rc);
493
494 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
495
496 RTFILE hFile;
497 rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
498 AssertRCReturn(rc, rc);
499
500 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
501 while (RT_SUCCESS(rc))
502 {
503 ATSSRVREPLY Reply;
504 RT_ZERO(Reply);
505
506 rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
507 if (RT_SUCCESS(rc))
508 {
509 /* Extract received CRC32 checksum. */
510 const size_t cbCrc32 = sizeof(uint32_t); /* Skip CRC32 in payload for actual CRC verification. */
511
512 uint32_t uSrcCrc32;
513 memcpy(&uSrcCrc32, Reply.pvPayload, cbCrc32);
514
515 if (uSrcCrc32)
516 {
517 const uint32_t uDstCrc32 = RTCrc32((uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32);
518
519 Log2Func(("uSrcCrc32=%#x, cbRead=%zu -> uDstCrc32=%#x\n"
520 "%.*Rhxd\n",
521 uSrcCrc32, Reply.cbPayload - cbCrc32, uDstCrc32,
522 RT_MIN(64, Reply.cbPayload - cbCrc32), (uint8_t *)Reply.pvPayload + cbCrc32));
523
524 if (uSrcCrc32 != uDstCrc32)
525 rc = VERR_TAR_CHKSUM_MISMATCH; /** @todo Fudge! */
526 }
527
528 if (RT_SUCCESS(rc))
529 {
530 if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
531 && Reply.pvPayload
532 && Reply.cbPayload)
533 {
534 rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32, NULL);
535 }
536 else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
537 {
538 rc = VINF_EOF;
539 }
540 else
541 {
542 AssertMsgFailed(("Got unexpected reply '%s'", Reply.szOp));
543 rc = VERR_NOT_SUPPORTED;
544 }
545 }
546 }
547
548 audioTestSvcClientReplyDestroy(&Reply);
549
550 int rc2 = audioTestSvcClientSendAck(pClient);
551 if (rc == VINF_SUCCESS) /* Might be VINF_EOF already. */
552 rc = rc2;
553
554 if (rc == VINF_EOF)
555 break;
556 }
557
558 int rc2 = RTFileClose(hFile);
559 if (RT_SUCCESS(rc))
560 rc = rc2;
561
562 return rc;
563}
564
565/**
566 * Disconnects from an ATS server, internal version.
567 *
568 * @returns VBox status code.
569 * @param pClient Client to disconnect.
570 */
571static int audioTestSvcClientCloseInternal(PATSCLIENT pClient)
572{
573 if (!pClient->pTransportClient) /* Not connected (yet)? Bail out early. */
574 return VINF_SUCCESS;
575
576 int rc = audioTestSvcClientDoBye(pClient);
577 if (RT_SUCCESS(rc))
578 {
579 if (pClient->pTransport->pfnNotifyBye)
580 pClient->pTransport->pfnNotifyBye(pClient->pTransportInst, pClient->pTransportClient);
581 }
582
583 return rc;
584}
585
586/**
587 * Disconnects from an ATS server.
588 *
589 * @returns VBox status code.
590 * @param pClient Client to disconnect.
591 */
592int AudioTestSvcClientClose(PATSCLIENT pClient)
593{
594 return audioTestSvcClientCloseInternal(pClient);
595}
596
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette