VirtualBox

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

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

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.8 KB
Line 
1/* $Id: AudioTestServiceClient.cpp 93115 2022-01-01 11:31:46Z 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-2022 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 audioTestSvcClientDisconnectInternal(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 */ audioTestSvcClientDisconnectInternal(pClient);
320
321 if (pClient->pTransport)
322 {
323 pClient->pTransport->pfnDestroy(pClient->pTransportInst);
324 pClient->pTransportInst = NULL; /* Invalidate pointer. */
325 }
326}
327
328/**
329 * Handles a command line option.
330 *
331 * @returns VBox status code.
332 * @param pClient Client to handle option for.
333 * @param ch Option (short) to handle.
334 * @param pVal Option union to store the result in on success.
335 */
336int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal)
337{
338 AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER); /* Must be created first via AudioTestSvcClientCreate(). */
339 if (!pClient->pTransport->pfnOption)
340 return VERR_GETOPT_UNKNOWN_OPTION;
341 return pClient->pTransport->pfnOption(pClient->pTransportInst, ch, pVal);
342}
343
344/**
345 * Connects to an ATS peer, extended version.
346 *
347 * @returns VBox status code.
348 * @param pClient Client to connect.
349 * @param msTimeout Timeout (in ms) waiting for a connection to be established.
350 * Use RT_INDEFINITE_WAIT to wait indefinitely.
351 */
352int AudioTestSvcClientConnectEx(PATSCLIENT pClient, RTMSINTERVAL msTimeout)
353{
354 if (pClient->pTransportClient)
355 return VERR_NET_ALREADY_CONNECTED;
356
357 int rc = pClient->pTransport->pfnStart(pClient->pTransportInst);
358 if (RT_SUCCESS(rc))
359 {
360 rc = pClient->pTransport->pfnWaitForConnect(pClient->pTransportInst,
361 msTimeout, NULL /* pfFromServer */, &pClient->pTransportClient);
362 if (RT_SUCCESS(rc))
363 {
364 rc = audioTestSvcClientDoGreet(pClient);
365 }
366 }
367
368 if (RT_FAILURE(rc))
369 LogRelFunc(("Connecting to server (%RU32ms timeout) failed with %Rrc\n", msTimeout, rc));
370
371 return rc;
372}
373
374/**
375 * Connects to an ATS peer.
376 *
377 * @returns VBox status code.
378 * @param pClient Client to connect.
379 */
380int AudioTestSvcClientConnect(PATSCLIENT pClient)
381{
382 return AudioTestSvcClientConnectEx(pClient, 30 * 1000 /* msTimeout */);
383}
384
385/**
386 * Tells the server to begin a new test set.
387 *
388 * @returns VBox status code.
389 * @param pClient Client to issue command for.
390 * @param pszTag Tag to use for the test set to begin.
391 */
392int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
393{
394 ATSPKTREQTSETBEG Req;
395
396 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
397 AssertRCReturn(rc, rc);
398
399 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
400
401 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
402 if (RT_SUCCESS(rc))
403 rc = audioTestSvcClientRecvAck(pClient);
404
405 return rc;
406}
407
408/**
409 * Tells the server to end a runing test set.
410 *
411 * @returns VBox status code.
412 * @param pClient Client to issue command for.
413 * @param pszTag Tag of test set to end.
414 */
415int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
416{
417 ATSPKTREQTSETEND Req;
418
419 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
420 AssertRCReturn(rc, rc);
421
422 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
423
424 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
425 if (RT_SUCCESS(rc))
426 rc = audioTestSvcClientRecvAck(pClient);
427
428 return rc;
429}
430
431/**
432 * Tells the server to play a (test) tone.
433 *
434 * @returns VBox status code.
435 * @param pClient Client to issue command for.
436 * @param pToneParms Tone parameters to use.
437 * @note How (and if) the server plays a tone depends on the actual implementation side.
438 */
439int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
440{
441 ATSPKTREQTONEPLAY Req;
442
443 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
444
445 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
446
447 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
448 if (RT_SUCCESS(rc))
449 rc = audioTestSvcClientRecvAck(pClient);
450
451 return rc;
452}
453
454/**
455 * Tells the server to record a (test) tone.
456 *
457 * @returns VBox status code.
458 * @param pClient Client to issue command for.
459 * @param pToneParms Tone parameters to use.
460 * @note How (and if) the server plays a tone depends on the actual implementation side.
461 */
462int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
463{
464 ATSPKTREQTONEREC Req;
465
466 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
467
468 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
469
470 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
471 if (RT_SUCCESS(rc))
472 rc = audioTestSvcClientRecvAck(pClient);
473
474 return rc;
475}
476
477/**
478 * Tells the server to send (download) a (packed up) test set archive.
479 * The test set must not be running / open anymore.
480 *
481 * @returns VBox status code.
482 * @param pClient Client to issue command for.
483 * @param pszTag Tag of test set to send.
484 * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
485 */
486int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
487{
488 ATSPKTREQTSETSND Req;
489
490 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
491 AssertRCReturn(rc, rc);
492
493 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
494
495 RTFILE hFile;
496 rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
497 AssertRCReturn(rc, rc);
498
499 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
500 while (RT_SUCCESS(rc))
501 {
502 ATSSRVREPLY Reply;
503 RT_ZERO(Reply);
504
505 rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
506 if (RT_SUCCESS(rc))
507 {
508 /* Extract received CRC32 checksum. */
509 const size_t cbCrc32 = sizeof(uint32_t); /* Skip CRC32 in payload for actual CRC verification. */
510
511 uint32_t uSrcCrc32;
512 memcpy(&uSrcCrc32, Reply.pvPayload, cbCrc32);
513
514 if (uSrcCrc32)
515 {
516 const uint32_t uDstCrc32 = RTCrc32((uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32);
517
518 Log2Func(("uSrcCrc32=%#x, cbRead=%zu -> uDstCrc32=%#x\n"
519 "%.*Rhxd\n",
520 uSrcCrc32, Reply.cbPayload - cbCrc32, uDstCrc32,
521 RT_MIN(64, Reply.cbPayload - cbCrc32), (uint8_t *)Reply.pvPayload + cbCrc32));
522
523 if (uSrcCrc32 != uDstCrc32)
524 rc = VERR_TAR_CHKSUM_MISMATCH; /** @todo Fudge! */
525 }
526
527 if (RT_SUCCESS(rc))
528 {
529 if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
530 && Reply.pvPayload
531 && Reply.cbPayload)
532 {
533 rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32, NULL);
534 }
535 else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
536 {
537 rc = VINF_EOF;
538 }
539 else
540 {
541 AssertMsgFailed(("Got unexpected reply '%s'", Reply.szOp));
542 rc = VERR_NOT_SUPPORTED;
543 }
544 }
545 }
546
547 audioTestSvcClientReplyDestroy(&Reply);
548
549 int rc2 = audioTestSvcClientSendAck(pClient);
550 if (rc == VINF_SUCCESS) /* Might be VINF_EOF already. */
551 rc = rc2;
552
553 if (rc == VINF_EOF)
554 break;
555 }
556
557 int rc2 = RTFileClose(hFile);
558 if (RT_SUCCESS(rc))
559 rc = rc2;
560
561 return rc;
562}
563
564/**
565 * Disconnects from an ATS server, internal version.
566 *
567 * @returns VBox status code.
568 * @param pClient Client to disconnect.
569 */
570static int audioTestSvcClientDisconnectInternal(PATSCLIENT pClient)
571{
572 if (!pClient->pTransportClient) /* Not connected (yet)? Bail out early. */
573 return VINF_SUCCESS;
574
575 int rc = audioTestSvcClientDoBye(pClient);
576 if (RT_SUCCESS(rc))
577 {
578 if (pClient->pTransport->pfnNotifyBye)
579 pClient->pTransport->pfnNotifyBye(pClient->pTransportInst, pClient->pTransportClient);
580
581 pClient->pTransport->pfnDisconnect(pClient->pTransportInst, pClient->pTransportClient);
582 pClient->pTransportClient = NULL;
583
584 pClient->pTransport->pfnStop(pClient->pTransportInst);
585 }
586
587 return rc;
588}
589
590/**
591 * Disconnects from an ATS server.
592 *
593 * @returns VBox status code.
594 * @param pClient Client to disconnect.
595 */
596int AudioTestSvcClientDisconnect(PATSCLIENT pClient)
597{
598 return audioTestSvcClientDisconnectInternal(pClient);
599}
600
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