VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

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