VirtualBox

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

Last change on this file since 98638 was 98103, checked in by vboxsync, 2 years 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.1 KB
Line 
1/* $Id: AudioTestServiceClient.cpp 98103 2023-01-17 14:15: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-2023 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 * @returns VBox status code.
232 * @param pReqHdr Request header to initialize.
233 * @param cbReq Size (in bytes) the request will have (does *not* include payload).
234 * @param pszOp Operation to perform with the request.
235 * @param cbPayload Size (in bytes) of payload that will follow the header. Optional and can be 0.
236 */
237DECLINLINE (void) audioTestSvcClientReqHdrInit(PATSPKTHDR pReqHdr, size_t cbReq, const char *pszOp, size_t cbPayload)
238{
239 AssertReturnVoid(strlen(pszOp) >= 2);
240 AssertReturnVoid(strlen(pszOp) <= ATSPKT_OPCODE_MAX_LEN);
241
242 /** @todo Validate opcode. */
243
244 RT_BZERO(pReqHdr, sizeof(ATSPKTHDR));
245
246 memcpy(pReqHdr->achOpcode, pszOp, strlen(pszOp));
247 pReqHdr->uCrc32 = 0; /** @todo Do CRC-32 calculation. */
248 pReqHdr->cb = (uint32_t)cbReq + (uint32_t)cbPayload;
249
250 Assert(pReqHdr->cb <= ATSPKT_MAX_SIZE);
251}
252
253/**
254 * Sends an acknowledege response back to the server.
255 *
256 * @returns VBox status code.
257 * @param pClient Client to send command for.
258 */
259static int audioTestSvcClientSendAck(PATSCLIENT pClient)
260{
261 ATSPKTHDR Req;
262 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), "ACK ", 0);
263
264 return audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
265}
266
267/**
268 * Sends a greeting command (handshake) to an ATS server.
269 *
270 * @returns VBox status code.
271 * @param pClient Client to send command for.
272 */
273static int audioTestSvcClientDoGreet(PATSCLIENT pClient)
274{
275 ATSPKTREQHOWDY Req;
276 Req.uVersion = ATS_PROTOCOL_VS;
277 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_HOWDY, 0);
278 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
279 if (RT_SUCCESS(rc))
280 rc = audioTestSvcClientRecvAck(pClient);
281 return rc;
282}
283
284/**
285 * Tells the ATS server that we want to disconnect.
286 *
287 * @returns VBox status code.
288 * @param pClient Client to disconnect.
289 */
290static int audioTestSvcClientDoBye(PATSCLIENT pClient)
291{
292 ATSPKTHDR Req;
293 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), ATSPKT_OPCODE_BYE, 0);
294 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
295 if (RT_SUCCESS(rc))
296 rc = audioTestSvcClientRecvAck(pClient);
297 return rc;
298}
299
300/**
301 * Creates an ATS client.
302 *
303 * @returns VBox status code.
304 * @param pClient Client to create.
305 */
306int AudioTestSvcClientCreate(PATSCLIENT pClient)
307{
308 audioTestSvcClientInit(pClient);
309
310 /*
311 * The default transporter is the first one.
312 */
313 pClient->pTransport = g_apTransports[0]; /** @todo Make this dynamic. */
314
315 return pClient->pTransport->pfnCreate(&pClient->pTransportInst);
316}
317
318/**
319 * Destroys an ATS client.
320 *
321 * @returns VBox status code.
322 * @param pClient Client to destroy.
323 */
324void AudioTestSvcClientDestroy(PATSCLIENT pClient)
325{
326 if (!pClient)
327 return;
328
329 /* ignore rc */ audioTestSvcClientDisconnectInternal(pClient);
330
331 if (pClient->pTransport)
332 {
333 pClient->pTransport->pfnDestroy(pClient->pTransportInst);
334 pClient->pTransportInst = NULL; /* Invalidate pointer. */
335 }
336}
337
338/**
339 * Handles a command line option.
340 *
341 * @returns VBox status code.
342 * @param pClient Client to handle option for.
343 * @param ch Option (short) to handle.
344 * @param pVal Option union to store the result in on success.
345 */
346int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal)
347{
348 AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER); /* Must be created first via AudioTestSvcClientCreate(). */
349 if (!pClient->pTransport->pfnOption)
350 return VERR_GETOPT_UNKNOWN_OPTION;
351 return pClient->pTransport->pfnOption(pClient->pTransportInst, ch, pVal);
352}
353
354/**
355 * Connects to an ATS peer, extended version.
356 *
357 * @returns VBox status code.
358 * @param pClient Client to connect.
359 * @param msTimeout Timeout (in ms) waiting for a connection to be established.
360 * Use RT_INDEFINITE_WAIT to wait indefinitely.
361 */
362int AudioTestSvcClientConnectEx(PATSCLIENT pClient, RTMSINTERVAL msTimeout)
363{
364 if (pClient->pTransportClient)
365 return VERR_NET_ALREADY_CONNECTED;
366
367 int rc = pClient->pTransport->pfnStart(pClient->pTransportInst);
368 if (RT_SUCCESS(rc))
369 {
370 rc = pClient->pTransport->pfnWaitForConnect(pClient->pTransportInst,
371 msTimeout, NULL /* pfFromServer */, &pClient->pTransportClient);
372 if (RT_SUCCESS(rc))
373 {
374 rc = audioTestSvcClientDoGreet(pClient);
375 }
376 }
377
378 if (RT_FAILURE(rc))
379 LogRelFunc(("Connecting to server (%RU32ms timeout) failed with %Rrc\n", msTimeout, rc));
380
381 return rc;
382}
383
384/**
385 * Connects to an ATS peer.
386 *
387 * @returns VBox status code.
388 * @param pClient Client to connect.
389 */
390int AudioTestSvcClientConnect(PATSCLIENT pClient)
391{
392 return AudioTestSvcClientConnectEx(pClient, 30 * 1000 /* msTimeout */);
393}
394
395/**
396 * Tells the server to begin a new test set.
397 *
398 * @returns VBox status code.
399 * @param pClient Client to issue command for.
400 * @param pszTag Tag to use for the test set to begin.
401 */
402int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
403{
404 ATSPKTREQTSETBEG Req;
405
406 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
407 AssertRCReturn(rc, rc);
408
409 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
410
411 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
412 if (RT_SUCCESS(rc))
413 rc = audioTestSvcClientRecvAck(pClient);
414
415 return rc;
416}
417
418/**
419 * Tells the server to end a runing test set.
420 *
421 * @returns VBox status code.
422 * @param pClient Client to issue command for.
423 * @param pszTag Tag of test set to end.
424 */
425int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
426{
427 ATSPKTREQTSETEND Req;
428
429 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
430 AssertRCReturn(rc, rc);
431
432 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
433
434 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
435 if (RT_SUCCESS(rc))
436 rc = audioTestSvcClientRecvAck(pClient);
437
438 return rc;
439}
440
441/**
442 * Tells the server to play a (test) tone.
443 *
444 * @returns VBox status code.
445 * @param pClient Client to issue command for.
446 * @param pToneParms Tone parameters to use.
447 * @note How (and if) the server plays a tone depends on the actual implementation side.
448 */
449int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
450{
451 ATSPKTREQTONEPLAY Req;
452
453 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
454
455 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
456
457 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
458 if (RT_SUCCESS(rc))
459 rc = audioTestSvcClientRecvAck(pClient);
460
461 return rc;
462}
463
464/**
465 * Tells the server to record a (test) tone.
466 *
467 * @returns VBox status code.
468 * @param pClient Client to issue command for.
469 * @param pToneParms Tone parameters to use.
470 * @note How (and if) the server plays a tone depends on the actual implementation side.
471 */
472int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
473{
474 ATSPKTREQTONEREC Req;
475
476 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
477
478 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
479
480 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
481 if (RT_SUCCESS(rc))
482 rc = audioTestSvcClientRecvAck(pClient);
483
484 return rc;
485}
486
487/**
488 * Tells the server to send (download) a (packed up) test set archive.
489 * The test set must not be running / open anymore.
490 *
491 * @returns VBox status code.
492 * @param pClient Client to issue command for.
493 * @param pszTag Tag of test set to send.
494 * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
495 */
496int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
497{
498 ATSPKTREQTSETSND Req;
499
500 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
501 AssertRCReturn(rc, rc);
502
503 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
504
505 RTFILE hFile;
506 rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
507 AssertRCReturn(rc, rc);
508
509 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
510 while (RT_SUCCESS(rc))
511 {
512 ATSSRVREPLY Reply;
513 RT_ZERO(Reply);
514
515 rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
516 if (RT_SUCCESS(rc))
517 {
518 /* Extract received CRC32 checksum. */
519 const size_t cbCrc32 = sizeof(uint32_t); /* Skip CRC32 in payload for actual CRC verification. */
520
521 uint32_t uSrcCrc32;
522 memcpy(&uSrcCrc32, Reply.pvPayload, cbCrc32);
523
524 if (uSrcCrc32)
525 {
526 const uint32_t uDstCrc32 = RTCrc32((uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32);
527
528 Log2Func(("uSrcCrc32=%#x, cbRead=%zu -> uDstCrc32=%#x\n"
529 "%.*Rhxd\n",
530 uSrcCrc32, Reply.cbPayload - cbCrc32, uDstCrc32,
531 RT_MIN(64, Reply.cbPayload - cbCrc32), (uint8_t *)Reply.pvPayload + cbCrc32));
532
533 if (uSrcCrc32 != uDstCrc32)
534 rc = VERR_TAR_CHKSUM_MISMATCH; /** @todo Fudge! */
535 }
536
537 if (RT_SUCCESS(rc))
538 {
539 if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
540 && Reply.pvPayload
541 && Reply.cbPayload)
542 {
543 rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32, NULL);
544 }
545 else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
546 {
547 rc = VINF_EOF;
548 }
549 else
550 {
551 AssertMsgFailed(("Got unexpected reply '%s'", Reply.szOp));
552 rc = VERR_NOT_SUPPORTED;
553 }
554 }
555 }
556
557 audioTestSvcClientReplyDestroy(&Reply);
558
559 int rc2 = audioTestSvcClientSendAck(pClient);
560 if (rc == VINF_SUCCESS) /* Might be VINF_EOF already. */
561 rc = rc2;
562
563 if (rc == VINF_EOF)
564 break;
565 }
566
567 int rc2 = RTFileClose(hFile);
568 if (RT_SUCCESS(rc))
569 rc = rc2;
570
571 return rc;
572}
573
574/**
575 * Disconnects from an ATS server, internal version.
576 *
577 * @returns VBox status code.
578 * @param pClient Client to disconnect.
579 */
580static int audioTestSvcClientDisconnectInternal(PATSCLIENT pClient)
581{
582 if (!pClient->pTransportClient) /* Not connected (yet)? Bail out early. */
583 return VINF_SUCCESS;
584
585 int rc = audioTestSvcClientDoBye(pClient);
586 if (RT_SUCCESS(rc))
587 {
588 if (pClient->pTransport->pfnNotifyBye)
589 pClient->pTransport->pfnNotifyBye(pClient->pTransportInst, pClient->pTransportClient);
590
591 pClient->pTransport->pfnDisconnect(pClient->pTransportInst, pClient->pTransportClient);
592 pClient->pTransportClient = NULL;
593
594 pClient->pTransport->pfnStop(pClient->pTransportInst);
595 }
596
597 return rc;
598}
599
600/**
601 * Disconnects from an ATS server.
602 *
603 * @returns VBox status code.
604 * @param pClient Client to disconnect.
605 */
606int AudioTestSvcClientDisconnect(PATSCLIENT pClient)
607{
608 return audioTestSvcClientDisconnectInternal(pClient);
609}
610
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