VirtualBox

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

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

Audio/VKAT: More code for TCP/IP connection mode handling; don't do things less implicitly but let the caller (or rather test driver) choose how to connect to the guest/host instead. ​bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.3 KB
Line 
1/* $Id: AudioTestServiceClient.cpp 90887 2021-08-25 16:20:14Z 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 AssertReturn(pPktHdr->cb >= sizeof(ATSPKTHDR), VERR_NET_PROTOCOL_ERROR);
112 pReply->cbPayload = pPktHdr->cb - sizeof(ATSPKTHDR);
113 Log3Func(("szOp=%.8s, cb=%RU32\n", pPktHdr->achOpcode, pPktHdr->cb));
114 if (pReply->cbPayload)
115 {
116 pReply->pvPayload = RTMemDup((uint8_t *)pPktHdr + sizeof(ATSPKTHDR), pReply->cbPayload);
117 }
118 else
119 pReply->pvPayload = NULL;
120
121 if ( !pReply->cbPayload
122 && !fNoDataOk)
123 {
124 rc = VERR_NET_PROTOCOL_ERROR;
125 }
126 else
127 {
128 memcpy(&pReply->szOp, &pPktHdr->achOpcode, sizeof(pReply->szOp));
129 }
130
131 RTMemFree(pPktHdr);
132 pPktHdr = NULL;
133 }
134
135 LogFlowFuncLeaveRC(rc);
136 return rc;
137}
138
139/**
140 * Receives a reply for an ATS server and checks if it is an acknowledge (success) one.
141 *
142 * @returns VBox status code.
143 * @retval VERR_NET_PROTOCOL_ERROR if the reply indicates a failure.
144 * @param pClient Client to receive reply for.
145 */
146static int audioTestSvcClientRecvAck(PATSCLIENT pClient)
147{
148 ATSSRVREPLY Reply;
149 RT_ZERO(Reply);
150
151 int rc = audioTestSvcClientRecvReply(pClient, &Reply, true /* fNoDataOk */);
152 if (RT_SUCCESS(rc))
153 {
154 if (RTStrNCmp(Reply.szOp, "ACK ", ATSPKT_OPCODE_MAX_LEN) != 0)
155 rc = VERR_NET_PROTOCOL_ERROR;
156
157 audioTestSvcClientReplyDestroy(&Reply);
158 }
159
160 return rc;
161}
162
163/**
164 * Sends a message plus optional payload to an ATS server.
165 *
166 * @returns VBox status code.
167 * @param pClient Client to send message for.
168 * @param pvHdr Pointer to header data to send.
169 * @param cbHdr Size (in bytes) of \a pvHdr to send.
170 */
171static int audioTestSvcClientSendMsg(PATSCLIENT pClient, void *pvHdr, size_t cbHdr)
172{
173 RT_NOREF(cbHdr);
174 AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER);
175 AssertPtrReturn(pClient->pTransportInst, VERR_WRONG_ORDER);
176 AssertPtrReturn(pClient->pTransportClient, VERR_NET_NOT_CONNECTED);
177 return pClient->pTransport->pfnSendPkt(pClient->pTransportInst, pClient->pTransportClient, (PCATSPKTHDR)pvHdr);
178}
179
180/**
181 * Initializes a client request header.
182 *
183 * @returns VBox status code.
184 * @param pReqHdr Request header to initialize.
185 * @param cbReq Size (in bytes) the request will have (does *not* include payload).
186 * @param pszOp Operation to perform with the request.
187 * @param cbPayload Size (in bytes) of payload that will follow the header. Optional and can be 0.
188 */
189DECLINLINE (void) audioTestSvcClientReqHdrInit(PATSPKTHDR pReqHdr, size_t cbReq, const char *pszOp, size_t cbPayload)
190{
191 AssertReturnVoid(strlen(pszOp) >= 2);
192 AssertReturnVoid(strlen(pszOp) <= ATSPKT_OPCODE_MAX_LEN);
193
194 /** @todo Validate opcode. */
195
196 RT_BZERO(pReqHdr, sizeof(ATSPKTHDR));
197
198 memcpy(pReqHdr->achOpcode, pszOp, strlen(pszOp));
199 pReqHdr->uCrc32 = 0; /** @todo Do CRC-32 calculation. */
200 pReqHdr->cb = (uint32_t)cbReq + (uint32_t)cbPayload;
201
202 Assert(pReqHdr->cb <= ATSPKT_MAX_SIZE);
203}
204
205/**
206 * Sends an acknowledege response back to the server.
207 *
208 * @returns VBox status code.
209 * @param pClient Client to send command for.
210 */
211static int audioTestSvcClientSendAck(PATSCLIENT pClient)
212{
213 ATSPKTHDR Req;
214 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), "ACK ", 0);
215
216 return audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
217}
218
219/**
220 * Sends a greeting command (handshake) to an ATS server.
221 *
222 * @returns VBox status code.
223 * @param pClient Client to send command for.
224 */
225static int audioTestSvcClientDoGreet(PATSCLIENT pClient)
226{
227 ATSPKTREQHOWDY Req;
228 Req.uVersion = ATS_PROTOCOL_VS;
229 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_HOWDY, 0);
230 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
231 if (RT_SUCCESS(rc))
232 rc = audioTestSvcClientRecvAck(pClient);
233 return rc;
234}
235
236/**
237 * Tells the ATS server that we want to disconnect.
238 *
239 * @returns VBox status code.
240 * @param pClient Client to disconnect.
241 */
242static int audioTestSvcClientDoBye(PATSCLIENT pClient)
243{
244 ATSPKTHDR Req;
245 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), ATSPKT_OPCODE_BYE, 0);
246 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
247 if (RT_SUCCESS(rc))
248 rc = audioTestSvcClientRecvAck(pClient);
249 return rc;
250}
251
252/**
253 * Creates an ATS client.
254 *
255 * @returns VBox status code.
256 * @param pClient Client to create.
257 */
258int AudioTestSvcClientCreate(PATSCLIENT pClient)
259{
260 audioTestSvcClientInit(pClient);
261
262 /*
263 * The default transporter is the first one.
264 */
265 pClient->pTransport = g_apTransports[0]; /** @todo Make this dynamic. */
266
267 return pClient->pTransport->pfnCreate(&pClient->pTransportInst);
268}
269
270/**
271 * Destroys an ATS client.
272 *
273 * @returns VBox status code.
274 * @param pClient Client to destroy.
275 */
276void AudioTestSvcClientDestroy(PATSCLIENT pClient)
277{
278 if (!pClient)
279 return;
280
281 /* ignore rc */ audioTestSvcClientCloseInternal(pClient);
282
283 if (pClient->pTransport)
284 {
285 pClient->pTransport->pfnTerm(pClient->pTransportInst);
286 pClient->pTransport->pfnDestroy(pClient->pTransportInst);
287 pClient->pTransport = NULL;
288 }
289}
290
291/**
292 * Handles a command line option.
293 *
294 * @returns VBox status code.
295 * @param pClient Client to handle option for.
296 * @param ch Option (short) to handle.
297 * @param pVal Option union to store the result in on success.
298 */
299int AudioTestSvcClientHandleOption(PATSCLIENT pClient, int ch, PCRTGETOPTUNION pVal)
300{
301 AssertPtrReturn(pClient->pTransport, VERR_WRONG_ORDER); /* Must be created first via AudioTestSvcClientCreate(). */
302 if (!pClient->pTransport->pfnOption)
303 return VERR_GETOPT_UNKNOWN_OPTION;
304 return pClient->pTransport->pfnOption(pClient->pTransportInst, ch, pVal);
305}
306
307/**
308 * Connects to an ATS peer.
309 *
310 * @returns VBox status code.
311 * @param pClient Client to connect.
312 */
313int AudioTestSvcClientConnect(PATSCLIENT pClient)
314{
315 int rc = pClient->pTransport->pfnStart(pClient->pTransportInst);
316 if (RT_SUCCESS(rc))
317 {
318 rc = pClient->pTransport->pfnWaitForConnect(pClient->pTransportInst, &pClient->pTransportClient);
319 if (RT_SUCCESS(rc))
320 {
321 rc = audioTestSvcClientDoGreet(pClient);
322 }
323 }
324
325 return rc;
326}
327
328/**
329 * Tells the server to begin a new test set.
330 *
331 * @returns VBox status code.
332 * @param pClient Client to issue command for.
333 * @param pszTag Tag to use for the test set to begin.
334 */
335int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
336{
337 ATSPKTREQTSETBEG Req;
338
339 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
340 AssertRCReturn(rc, rc);
341
342 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
343
344 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
345 if (RT_SUCCESS(rc))
346 rc = audioTestSvcClientRecvAck(pClient);
347
348 return rc;
349}
350
351/**
352 * Tells the server to end a runing test set.
353 *
354 * @returns VBox status code.
355 * @param pClient Client to issue command for.
356 * @param pszTag Tag of test set to end.
357 */
358int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
359{
360 ATSPKTREQTSETEND Req;
361
362 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
363 AssertRCReturn(rc, rc);
364
365 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
366
367 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
368 if (RT_SUCCESS(rc))
369 rc = audioTestSvcClientRecvAck(pClient);
370
371 return rc;
372}
373
374/**
375 * Tells the server to play a (test) tone.
376 *
377 * @returns VBox status code.
378 * @param pClient Client to issue command for.
379 * @param pToneParms Tone parameters to use.
380 * @note How (and if) the server plays a tone depends on the actual implementation side.
381 */
382int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
383{
384 ATSPKTREQTONEPLAY Req;
385
386 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
387
388 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
389
390 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
391 if (RT_SUCCESS(rc))
392 rc = audioTestSvcClientRecvAck(pClient);
393
394 return rc;
395}
396
397/**
398 * Tells the server to record a (test) tone.
399 *
400 * @returns VBox status code.
401 * @param pClient Client to issue command for.
402 * @param pToneParms Tone parameters to use.
403 * @note How (and if) the server plays a tone depends on the actual implementation side.
404 */
405int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
406{
407 ATSPKTREQTONEREC Req;
408
409 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
410
411 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
412
413 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
414 if (RT_SUCCESS(rc))
415 rc = audioTestSvcClientRecvAck(pClient);
416
417 return rc;
418}
419
420/**
421 * Tells the server to send (download) a (packed up) test set archive.
422 * The test set must not be running / open anymore.
423 *
424 * @returns VBox status code.
425 * @param pClient Client to issue command for.
426 * @param pszTag Tag of test set to send.
427 * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
428 */
429int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
430{
431 ATSPKTREQTSETSND Req;
432
433 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
434 AssertRCReturn(rc, rc);
435
436 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
437
438 RTFILE hFile;
439 rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
440 AssertRCReturn(rc, rc);
441
442 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req));
443 while (RT_SUCCESS(rc))
444 {
445 ATSSRVREPLY Reply;
446 RT_ZERO(Reply);
447
448 rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
449 if (RT_SUCCESS(rc))
450 {
451 /* Extract received CRC32 checksum. */
452 const size_t cbCrc32 = sizeof(uint32_t); /* Skip CRC32 in payload for actual CRC verification. */
453
454 uint32_t uSrcCrc32;
455 memcpy(&uSrcCrc32, Reply.pvPayload, cbCrc32);
456
457 if (uSrcCrc32)
458 {
459 const uint32_t uDstCrc32 = RTCrc32((uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32);
460
461 Log2Func(("uSrcCrc32=%#x, cbRead=%zu -> uDstCrc32=%#x\n"
462 "%.*Rhxd\n",
463 uSrcCrc32, Reply.cbPayload - cbCrc32, uDstCrc32,
464 RT_MIN(64, Reply.cbPayload - cbCrc32), (uint8_t *)Reply.pvPayload + cbCrc32));
465
466 if (uSrcCrc32 != uDstCrc32)
467 rc = VERR_TAR_CHKSUM_MISMATCH; /** @todo Fudge! */
468 }
469
470 if (RT_SUCCESS(rc))
471 {
472 if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
473 && Reply.pvPayload
474 && Reply.cbPayload)
475 {
476 rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + cbCrc32, Reply.cbPayload - cbCrc32, NULL);
477 }
478 else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
479 {
480 rc = VINF_EOF;
481 }
482 else
483 {
484 AssertMsgFailed(("Got unexpected reply '%s'", Reply.szOp));
485 rc = VERR_NOT_SUPPORTED;
486 }
487 }
488 }
489
490 audioTestSvcClientReplyDestroy(&Reply);
491
492 int rc2 = audioTestSvcClientSendAck(pClient);
493 if (rc == VINF_SUCCESS) /* Might be VINF_EOF already. */
494 rc = rc2;
495
496 if (rc == VINF_EOF)
497 break;
498 }
499
500 int rc2 = RTFileClose(hFile);
501 if (RT_SUCCESS(rc))
502 rc = rc2;
503
504 return rc;
505}
506
507/**
508 * Disconnects from an ATS server, internal version.
509 *
510 * @returns VBox status code.
511 * @param pClient Client to disconnect.
512 */
513static int audioTestSvcClientCloseInternal(PATSCLIENT pClient)
514{
515 if (!pClient->pTransportClient) /* Not connected (yet)? Bail out early. */
516 return VINF_SUCCESS;
517
518 int rc = audioTestSvcClientDoBye(pClient);
519 if (RT_SUCCESS(rc))
520 {
521 if (pClient->pTransport->pfnNotifyBye)
522 pClient->pTransport->pfnNotifyBye(pClient->pTransportInst, pClient->pTransportClient);
523 }
524
525 return rc;
526}
527
528/**
529 * Disconnects from an ATS server.
530 *
531 * @returns VBox status code.
532 * @param pClient Client to disconnect.
533 */
534int AudioTestSvcClientClose(PATSCLIENT pClient)
535{
536 return audioTestSvcClientCloseInternal(pClient);
537}
538
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