VirtualBox

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

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

Audio/ATS: Implemented audioTestSvcClientDoBye(). bugref:10008

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

© 2023 Oracle
ContactPrivacy policyTerms of Use