VirtualBox

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

Last change on this file since 89636 was 89618, checked in by vboxsync, 4 years ago

Audio/ValKit: Implemented support for downloading (guest) test sets [Doxygen fix]. bugref:10008

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.4 KB
Line 
1/* $Id: AudioTestServiceClient.cpp 89618 2021-06-11 07:21:53Z 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 RTLOGGROUP_DEFAULT
25#include <VBox/log.h>
26
27#include <iprt/crc.h>
28#include <iprt/err.h>
29#include <iprt/file.h>
30#include <iprt/mem.h>
31#include <iprt/string.h>
32#include <iprt/tcp.h>
33
34#include "AudioTestService.h"
35#include "AudioTestServiceProtocol.h"
36#include "AudioTestServiceClient.h"
37
38/** @todo Use common defines between server protocol and this client. */
39
40/**
41 * A generic ATS reply, used by the client
42 * to process the incoming packets.
43 */
44typedef struct ATSSRVREPLY
45{
46 char szOp[ATSPKT_OPCODE_MAX_LEN];
47 void *pvPayload;
48 size_t cbPayload;
49} ATSSRVREPLY;
50/** Pointer to a generic ATS reply. */
51typedef struct ATSSRVREPLY *PATSSRVREPLY;
52
53
54/**
55 * Initializes an ATS client, internal version.
56 *
57 * @param pClient Client to initialize.
58 */
59static void audioTestSvcClientInit(PATSCLIENT pClient)
60{
61 pClient->cbHdr = 0;
62 pClient->hSock = NIL_RTSOCKET;
63}
64
65/**
66 * Destroys an ATS server reply.
67 *
68 * @param pReply Reply to destroy.
69 */
70static void audioTestSvcClientReplyDestroy(PATSSRVREPLY pReply)
71{
72 if (!pReply)
73 return;
74
75 if (pReply->pvPayload)
76 {
77 Assert(pReply->cbPayload);
78 RTMemFree(pReply->pvPayload);
79 pReply->pvPayload = NULL;
80 }
81
82 pReply->cbPayload = 0;
83}
84
85/**
86 * Receives a reply from an ATS server.
87 *
88 * @returns VBox status code.
89 * @param pClient Client to receive reply for.
90 * @param pReply Where to store the reply.
91 * The reply must be destroyed with audioTestSvcClientReplyDestroy() then.
92 * @param fNoDataOk If it's okay that the reply is not expected to have any payload.
93 */
94static int audioTestSvcClientRecvReply(PATSCLIENT pClient, PATSSRVREPLY pReply, bool fNoDataOk)
95{
96 int rc;
97
98 ATSPKTHDR Hdr;
99 size_t cbHdr = 0;
100 if (pClient->cbHdr)
101 {
102 memcpy(&Hdr, &pClient->abHdr, sizeof(Hdr));
103 cbHdr = pClient->cbHdr;
104 pClient->cbHdr = 0;
105 rc = VINF_SUCCESS;
106 }
107 else
108 {
109 rc = RTTcpRead(pClient->hSock, &Hdr, sizeof(Hdr), &cbHdr);
110 LogFlowFunc(("rc=%Rrc, hdr=%zu, hdr.cb=%zu, %.8s -> %.*Rhxd\n", rc, sizeof(Hdr), Hdr.cb, Hdr.achOpcode, RT_MIN(cbHdr, sizeof(ATSPKTHDR)), &Hdr));
111 }
112
113 if (cbHdr != sizeof(Hdr)) /* Check for bad packet sizes. */
114 return VERR_NET_PROTOCOL_ERROR;
115
116 if (RT_SUCCESS(rc))
117 {
118 if (Hdr.cb < sizeof(ATSPKTHDR))
119 return VERR_NET_PROTOCOL_ERROR;
120
121 if (Hdr.cb > ATSPKT_MAX_SIZE)
122 return VERR_NET_PROTOCOL_ERROR;
123
124 /** @todo Check opcode encoding. */
125
126 uint32_t cbPadding;
127 if (Hdr.cb % ATSPKT_ALIGNMENT)
128 cbPadding = ATSPKT_ALIGNMENT - (Hdr.cb % ATSPKT_ALIGNMENT);
129 else
130 cbPadding = 0;
131
132 if (Hdr.cb > sizeof(ATSPKTHDR))
133 {
134 pReply->cbPayload = (Hdr.cb - sizeof(ATSPKTHDR)) + cbPadding;
135 Log3Func(("cbPadding=%RU32, cbPayload: %RU32 -> %zu\n", cbPadding, Hdr.cb - sizeof(ATSPKTHDR), pReply->cbPayload));
136 AssertReturn(pReply->cbPayload <= ATSPKT_MAX_SIZE, VERR_BUFFER_OVERFLOW); /* Paranoia. */
137 }
138
139 if (pReply->cbPayload)
140 {
141 pReply->pvPayload = RTMemAlloc(pReply->cbPayload);
142 if (pReply->pvPayload)
143 {
144 size_t cbRead = 0;
145 rc = RTTcpRead(pClient->hSock, pReply->pvPayload, pReply->cbPayload, &cbRead);
146 if (RT_SUCCESS(rc))
147 {
148 Log3Func(("cbPayload=%zu -> cbRead=%zu\n", pReply->cbPayload, cbRead));
149 if (!cbRead)
150 {
151 memcpy(&pClient->abHdr, &Hdr, sizeof(pClient->abHdr));
152 if (!fNoDataOk)
153 rc = VERR_NET_PROTOCOL_ERROR;
154 }
155 else
156 {
157 while (cbPadding--)
158 {
159 Assert(cbRead);
160 cbRead--;
161 }
162 }
163 }
164
165 if (RT_SUCCESS(rc))
166 {
167 /** @todo Check CRC-32. */
168 pReply->cbPayload = cbRead;
169 /** @todo Re-allocate pvPayload to not store padding? */
170 }
171 else
172 audioTestSvcClientReplyDestroy(pReply);
173 }
174 else
175 rc = VERR_NO_MEMORY;
176 }
177
178 if (RT_SUCCESS(rc))
179 {
180 memcpy(pReply->szOp, Hdr.achOpcode, sizeof(pReply->szOp));
181 }
182 }
183
184 return rc;
185}
186
187/**
188 * Receives a reply for an ATS server and checks if it is an acknowledge (success) one.
189 *
190 * @returns VBox status code.
191 * @retval VERR_NET_PROTOCOL_ERROR if the reply indicates a failure.
192 * @param pClient Client to receive reply for.
193 */
194static int audioTestSvcClientRecvAck(PATSCLIENT pClient)
195{
196 ATSSRVREPLY Reply;
197 RT_ZERO(Reply);
198
199 int rc = audioTestSvcClientRecvReply(pClient, &Reply, true /* fNoDataOk */);
200 if (RT_SUCCESS(rc))
201 {
202 if (RTStrNCmp(Reply.szOp, "ACK ", ATSPKT_OPCODE_MAX_LEN) != 0)
203 rc = VERR_NET_PROTOCOL_ERROR;
204
205 audioTestSvcClientReplyDestroy(&Reply);
206 }
207
208 return rc;
209}
210
211/**
212 * Sends data over the transport to the server.
213 * For now only TCP/IP is implemented.
214 *
215 * @returns VBox status code.
216 * @param pClient Client to send data for.
217 * @param pvData Pointer to data to send.
218 * @param cbData Size (in bytes) of \a pvData to send.
219 */
220static int audioTestSvcClientSend(PATSCLIENT pClient, const void *pvData, size_t cbData)
221{
222 return RTTcpWrite(pClient->hSock, pvData, cbData);
223}
224
225/**
226 * Sends a message plus optional payload to an ATS server.
227 *
228 * @returns VBox status code.
229 * @param pClient Client to send message for.
230 * @param pvHdr Pointer to header data to send.
231 * @param cbHdr Size (in bytes) of \a pvHdr to send.
232 * @param pvPayload Pointer to payload to send. Optional.
233 * @param cbPayload Size (in bytes) of \a pvPayload to send. Set to 0 if no payload needed.
234 */
235static int audioTestSvcClientSendMsg(PATSCLIENT pClient,
236 void *pvHdr, size_t cbHdr, const void *pvPayload, size_t cbPayload)
237{
238 int rc = audioTestSvcClientSend(pClient, pvHdr, cbHdr);
239 if ( RT_SUCCESS(rc)
240 && cbPayload)
241 {
242 rc = audioTestSvcClientSend(pClient, (uint8_t *)pvPayload, cbPayload);
243 }
244
245 return rc;
246}
247
248/**
249 * Initializes a client request header.
250 *
251 * @returns VBox status code.
252 * @param pReqHdr Request header to initialize.
253 * @param cbReq Size (in bytes) the request will have (does *not* include payload).
254 * @param pszOp Operation to perform with the request.
255 * @param cbPayload Size (in bytes) of payload that will follow the header. Optional and can be 0.
256 */
257DECLINLINE (void) audioTestSvcClientReqHdrInit(PATSPKTHDR pReqHdr, size_t cbReq, const char *pszOp, size_t cbPayload)
258{
259 AssertReturnVoid(strlen(pszOp) >= 2);
260 AssertReturnVoid(strlen(pszOp) <= ATSPKT_OPCODE_MAX_LEN);
261
262 /** @todo Validate opcode. */
263
264 RT_BZERO(pReqHdr, sizeof(ATSPKTHDR));
265
266 memcpy(pReqHdr->achOpcode, pszOp, strlen(pszOp));
267 pReqHdr->uCrc32 = 0; /** @todo Do CRC-32 calculation. */
268 pReqHdr->cb = (uint32_t)cbReq + (uint32_t)cbPayload;
269
270 Assert(pReqHdr->cb <= ATSPKT_MAX_SIZE);
271}
272
273/**
274 * Sends an acknowledege response back to the server.
275 *
276 * @returns VBox status code.
277 * @param pClient Client to send command for.
278 */
279static int audioTestSvcClientSendAck(PATSCLIENT pClient)
280{
281 ATSPKTHDR Req;
282 audioTestSvcClientReqHdrInit(&Req, sizeof(Req), "ACK ", 0);
283
284 return audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
285}
286
287/**
288 * Sends a greeting command (handshake) to an ATS server.
289 *
290 * @returns VBox status code.
291 * @param pClient Client to send command for.
292 */
293static int audioTestSvcClientDoGreet(PATSCLIENT pClient)
294{
295 ATSPKTREQHOWDY Req;
296 Req.uVersion = ATS_PROTOCOL_VS;
297 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_HOWDY, 0);
298 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
299 if (RT_SUCCESS(rc))
300 rc = audioTestSvcClientRecvAck(pClient);
301 return rc;
302}
303
304/**
305 * Sends a disconnect command to an ATS server.
306 *
307 * @returns VBox status code.
308 * @param pClient Client to send command for.
309 */
310static int audioTestSvcClientDoBye(PATSCLIENT pClient)
311{
312 ATSPKTHDR Hdr;
313 audioTestSvcClientReqHdrInit(&Hdr, sizeof(Hdr), ATSPKT_OPCODE_BYE, 0);
314 int rc = audioTestSvcClientSendMsg(pClient, &Hdr, sizeof(Hdr), NULL, 0);
315 if (RT_SUCCESS(rc))
316 rc = audioTestSvcClientRecvAck(pClient);
317
318 return rc;
319}
320
321/**
322 * Connects to an ATS server.
323 *
324 * @returns VBox status code.
325 * @param pClient Client to connect.
326 * @param pszAddr Address to connect to. If NULL, 127.0.0.1 (localhost) will be used.
327 * @param uPort Port to connect. If set to 0, port 6052 (ATS_DEFAULT_PORT) will be used.
328 */
329int AudioTestSvcClientConnect(PATSCLIENT pClient, const char *pszAddr, uint32_t uPort)
330{
331 audioTestSvcClientInit(pClient);
332
333 int rc = RTTcpClientConnect(pszAddr ? pszAddr : ATS_TCP_HOST_DEFAULT_ADDR_STR, uPort == 0 ? ATS_TCP_HOST_DEFAULT_PORT : uPort, &pClient->hSock);
334 if (RT_SUCCESS(rc))
335 {
336 rc = audioTestSvcClientDoGreet(pClient);
337 }
338
339 return rc;
340}
341
342/**
343 * Tells the server to begin a new test set.
344 *
345 * @returns VBox status code.
346 * @param pClient Client to issue command for.
347 * @param pszTag Tag to use for the test set to begin.
348 */
349int AudioTestSvcClientTestSetBegin(PATSCLIENT pClient, const char *pszTag)
350{
351 ATSPKTREQTSETBEG Req;
352
353 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
354 AssertRCReturn(rc, rc);
355
356 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_BEGIN, 0);
357
358 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
359 if (RT_SUCCESS(rc))
360 rc = audioTestSvcClientRecvAck(pClient);
361
362 return rc;
363}
364
365/**
366 * Tells the server to end a runing test set.
367 *
368 * @returns VBox status code.
369 * @param pClient Client to issue command for.
370 * @param pszTag Tag of test set to end.
371 */
372int AudioTestSvcClientTestSetEnd(PATSCLIENT pClient, const char *pszTag)
373{
374 ATSPKTREQTSETEND Req;
375
376 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
377 AssertRCReturn(rc, rc);
378
379 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_END, 0);
380
381 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
382 if (RT_SUCCESS(rc))
383 rc = audioTestSvcClientRecvAck(pClient);
384
385 return rc;
386}
387
388/**
389 * Tells the server to play a (test) tone.
390 *
391 * @returns VBox status code.
392 * @param pClient Client to issue command for.
393 * @param pToneParms Tone parameters to use.
394 * @note How (and if) the server plays a tone depends on the actual implementation side.
395 */
396int AudioTestSvcClientTonePlay(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
397{
398 ATSPKTREQTONEPLAY Req;
399
400 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
401
402 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_PLAY, 0);
403
404 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
405 if (RT_SUCCESS(rc))
406 rc = audioTestSvcClientRecvAck(pClient);
407
408 return rc;
409}
410
411/**
412 * Tells the server to record a (test) tone.
413 *
414 * @returns VBox status code.
415 * @param pClient Client to issue command for.
416 * @param pToneParms Tone parameters to use.
417 * @note How (and if) the server plays a tone depends on the actual implementation side.
418 */
419int AudioTestSvcClientToneRecord(PATSCLIENT pClient, PAUDIOTESTTONEPARMS pToneParms)
420{
421 ATSPKTREQTONEREC Req;
422
423 memcpy(&Req.ToneParms, pToneParms, sizeof(AUDIOTESTTONEPARMS));
424
425 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TONE_RECORD, 0);
426
427 int rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
428 if (RT_SUCCESS(rc))
429 rc = audioTestSvcClientRecvAck(pClient);
430
431 return rc;
432}
433
434/**
435 * Tells the server to send (download) a (packed up) test set archive.
436 * The test set must not be running / open anymore.
437 *
438 * @returns VBox status code.
439 * @param pClient Client to issue command for.
440 * @param pszTag Tag of test set to send.
441 * @param pszPathOutAbs Absolute path where to store the downloaded test set archive.
442 */
443int AudioTestSvcClientTestSetDownload(PATSCLIENT pClient, const char *pszTag, const char *pszPathOutAbs)
444{
445 ATSPKTREQTSETSND Req;
446
447 int rc = RTStrCopy(Req.szTag, sizeof(Req.szTag), pszTag);
448 AssertRCReturn(rc, rc);
449
450 audioTestSvcClientReqHdrInit(&Req.Hdr, sizeof(Req), ATSPKT_OPCODE_TESTSET_SEND, 0);
451
452 RTFILE hFile;
453 rc = RTFileOpen(&hFile, pszPathOutAbs, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_WRITE);
454 AssertRCReturn(rc, rc);
455
456 rc = audioTestSvcClientSendMsg(pClient, &Req, sizeof(Req), NULL, 0);
457 while (RT_SUCCESS(rc))
458 {
459 ATSSRVREPLY Reply;
460 RT_ZERO(Reply);
461
462 rc = audioTestSvcClientRecvReply(pClient, &Reply, false /* fNoDataOk */);
463 AssertRCBreak(rc);
464
465 if ( RTStrNCmp(Reply.szOp, "DATA ", ATSPKT_OPCODE_MAX_LEN) == 0
466 && Reply.pvPayload
467 && Reply.cbPayload)
468 {
469 /** @todo Skip uCrc32 for now. */
470 rc = RTFileWrite(hFile, (uint8_t *)Reply.pvPayload + 4, Reply.cbPayload - 4, NULL);
471 }
472 else if (RTStrNCmp(Reply.szOp, "DATA EOF", ATSPKT_OPCODE_MAX_LEN) == 0)
473 {
474 rc = VINF_EOF;
475 }
476 else
477 AssertFailedStmt(rc = VERR_NET_PROTOCOL_ERROR);
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 int rc = audioTestSvcClientDoBye(pClient);
505 if (RT_SUCCESS(rc))
506 rc = RTTcpClientClose(pClient->hSock);
507
508 return rc;
509}
510
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