VirtualBox

source: vbox/trunk/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp@ 99393

Last change on this file since 99393 was 99393, checked in by vboxsync, 20 months ago

Guest Control: Completely revamped / overhauled the (now legacy) stream output parsing code and added lots of documentation to it. This way it should be a lot clearer what it's supposed to be doing. Also, this now should fix some nasty bugs in that area we had in the past especially with some Linux guests (i.e. OL6), which sometimes send output data in a very unsteady manner. Also overhauled the testcases while at it [build fix].

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.2 KB
Line 
1/* $Id: tstGuestCtrlParseBuffer.cpp 99393 2023-04-13 17:09:53Z vboxsync $ */
2/** @file
3 * Tests for VBoxService toolbox output streams.
4 */
5
6/*
7 * Copyright (C) 2011-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN
33#include <VBox/err.h>
34#include <VBox/log.h>
35
36#include "../include/GuestCtrlImplPrivate.h"
37
38using namespace com;
39
40#include <iprt/env.h>
41#include <iprt/file.h>
42#include <iprt/test.h>
43#include <iprt/rand.h>
44#include <iprt/stream.h>
45
46#ifndef BYTE
47# define BYTE uint8_t
48#endif
49
50
51/*********************************************************************************************************************************
52* Defined Constants And Macros *
53*********************************************************************************************************************************/
54/** Defines a test entry string size (in bytes). */
55#define TST_STR_BYTES(a_sz) (sizeof(a_sz) - 1)
56/** Defines a test entry string, followed by its size (in bytes). */
57#define TST_STR_AND_BYTES(a_sz) a_sz, (sizeof(a_sz) - 1)
58/** Defines the termination sequence for a single key/value pair. */
59#define TST_STR_VAL_TRM GUESTTOOLBOX_STRM_TERM_PAIR_STR
60/** Defines the termination sequence for a single stream block. */
61#define TST_STR_BLK_TRM GUESTTOOLBOX_STRM_TERM_BLOCK_STR
62/** Defines the termination sequence for the stream. */
63#define TST_STR_STM_TRM GUESTTOOLBOX_STRM_TERM_STREAM_STR
64
65
66/*********************************************************************************************************************************
67* Structures and Typedefs *
68*********************************************************************************************************************************/
69typedef struct VBOXGUESTCTRL_BUFFER_VALUE
70{
71 char *pszValue;
72} VBOXGUESTCTRL_BUFFER_VALUE, *PVBOXGUESTCTRL_BUFFER_VALUE;
73typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE > GuestBufferMap;
74typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE >::iterator GuestBufferMapIter;
75typedef std::map< RTCString, VBOXGUESTCTRL_BUFFER_VALUE >::const_iterator GuestBufferMapIterConst;
76
77
78/*********************************************************************************************************************************
79* Global Variables *
80*********************************************************************************************************************************/
81char szUnterm1[] = { 'a', 's', 'd', 'f' };
82char szUnterm2[] = { 'f', 'o', 'o', '3', '=', 'b', 'a', 'r', '3' };
83
84PRTLOGGER g_pLog = NULL;
85
86/**
87 * Tests single block parsing.
88 */
89static struct
90{
91 const char *pbData;
92 size_t cbData;
93 uint32_t offStart;
94 uint32_t offAfter;
95 uint32_t cMapElements;
96 int iResult;
97} g_aTestBlocks[] =
98{
99 /* Invalid stuff. */
100 { NULL, 0, 0, 0, 0, VERR_INVALID_POINTER },
101 { NULL, 512, 0, 0, 0, VERR_INVALID_POINTER },
102 { "", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
103 { "", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
104 { "foo=bar1", 0, 0, 0, 0, VERR_INVALID_PARAMETER },
105 { "foo=bar2", 0, 50, 50, 0, VERR_INVALID_PARAMETER },
106 /* Has a empty key (not allowed). */
107 { TST_STR_AND_BYTES("=test2" TST_STR_VAL_TRM), 0, TST_STR_BYTES(""), 0, VERR_INVALID_PARAMETER },
108 /* Empty buffers, i.e. nothing to process. */
109 /* Index 6*/
110 { "", 1, 0, 0, 0, VINF_SUCCESS },
111 { TST_STR_VAL_TRM, 1, 0, 0, 0, VINF_SUCCESS },
112 /* Stream termination sequence. */
113 { TST_STR_AND_BYTES(TST_STR_STM_TRM), 0,
114 TST_STR_BYTES (TST_STR_STM_TRM), 0, VINF_EOF },
115 /* Trash after stream termination sequence (skipped / ignored). */
116 { TST_STR_AND_BYTES(TST_STR_STM_TRM "trash"), 0,
117 TST_STR_BYTES (TST_STR_STM_TRM "trash"), 0, VINF_EOF },
118 { TST_STR_AND_BYTES("a=b" TST_STR_STM_TRM), 0,
119 TST_STR_BYTES ("a=b" TST_STR_STM_TRM), 1, VINF_EOF },
120 { TST_STR_AND_BYTES("a=b" TST_STR_VAL_TRM "c=d" TST_STR_STM_TRM), 0,
121 TST_STR_BYTES ("a=b" TST_STR_VAL_TRM "c=d" TST_STR_STM_TRM), 2, VINF_EOF },
122 /* Unterminated values (missing separator, i.e. no valid pair). */
123 { TST_STR_AND_BYTES("test1"), 0, 0, 0, VINF_SUCCESS },
124 /* Has a NULL value (allowed). */
125 { TST_STR_AND_BYTES("test2=" TST_STR_VAL_TRM), 0,
126 TST_STR_BYTES ("test2="), 1, VINF_SUCCESS },
127 /* One completed pair only. */
128 { TST_STR_AND_BYTES("test3=test3" TST_STR_VAL_TRM), 0,
129 TST_STR_BYTES ("test3=test3"), 1, VINF_SUCCESS },
130 /* One completed pair, plus an unfinished pair (separator + terminator missing). */
131 { TST_STR_AND_BYTES("test4=test4" TST_STR_VAL_TRM "t41"), 0,
132 TST_STR_BYTES ("test4=test4" TST_STR_VAL_TRM), 1, VINF_SUCCESS },
133 /* Two completed pairs. */
134 { TST_STR_AND_BYTES("test5=test5" TST_STR_VAL_TRM "t51=t51" TST_STR_VAL_TRM), 0,
135 TST_STR_BYTES ("test5=test5" TST_STR_VAL_TRM "t51=t51"), 2, VINF_SUCCESS },
136 /* One complete block, next block unterminated. */
137 { TST_STR_AND_BYTES("a51=b51" TST_STR_VAL_TRM "c52=d52" TST_STR_BLK_TRM "e53=f53"), 0,
138 TST_STR_BYTES ("a51=b51" TST_STR_VAL_TRM "c52=d52" TST_STR_BLK_TRM), 2, VINF_SUCCESS },
139 /* Ditto. */
140 { TST_STR_AND_BYTES("test6=test6" TST_STR_BLK_TRM "t61=t61"), 0,
141 TST_STR_BYTES ("test6=test6" TST_STR_BLK_TRM), 1, VINF_SUCCESS },
142 /* Two complete pairs with a complete stream. */
143 { TST_STR_AND_BYTES("test61=" TST_STR_VAL_TRM "test611=test612" TST_STR_STM_TRM), 0,
144 TST_STR_BYTES ("test61=" TST_STR_VAL_TRM "test611=test612" TST_STR_STM_TRM), 2, VINF_EOF },
145 /* One complete block. */
146 { TST_STR_AND_BYTES("test7=test7" TST_STR_BLK_TRM), 0,
147 TST_STR_BYTES ("test7=test7"), 1, VINF_SUCCESS },
148 /* Ditto. */
149 { TST_STR_AND_BYTES("test81=test82" TST_STR_VAL_TRM "t81=t82" TST_STR_BLK_TRM), 0,
150 TST_STR_BYTES ("test81=test82" TST_STR_VAL_TRM "t81=t82"), 2, VINF_SUCCESS },
151 /* Good stuff, but with a second block -- should be *not* taken into account since
152 * we're only interested in parsing/handling the first object. */
153 { TST_STR_AND_BYTES("t91=t92" TST_STR_VAL_TRM "t93=t94" TST_STR_BLK_TRM "t95=t96" TST_STR_BLK_TRM), 0,
154 TST_STR_BYTES ("t91=t92" TST_STR_VAL_TRM "t93=t94" TST_STR_BLK_TRM), 2, VINF_SUCCESS },
155 /* Nasty stuff. */
156 /* iso 8859-1 encoding (?) of 'aou' all with diaeresis '=f' and 'ao' with diaeresis. */
157 { TST_STR_AND_BYTES("1\xe4\xf6\xfc=\x66\xe4\xf6" TST_STR_BLK_TRM), 0,
158 TST_STR_BYTES ("1\xe4\xf6\xfc=\x66\xe4\xf6"), 1, VINF_SUCCESS },
159 /* Like above, but after the first '\0' it adds 'ooo=aaa' all letters with diaeresis. */
160 { TST_STR_AND_BYTES("2\xe4\xf6\xfc=\x66\xe4\xf6" TST_STR_VAL_TRM "\xf6\xf6\xf6=\xe4\xe4\xe4"), 0,
161 TST_STR_BYTES ("2\xe4\xf6\xfc=\x66\xe4\xf6" TST_STR_VAL_TRM), 1, VINF_SUCCESS },
162 /* Some "real world" examples from VBoxService toolbox. */
163 { TST_STR_AND_BYTES("hdr_id=vbt_stat" TST_STR_VAL_TRM "hdr_ver=1" TST_STR_VAL_TRM "name=foo.txt" TST_STR_BLK_TRM), 0,
164 TST_STR_BYTES ("hdr_id=vbt_stat" TST_STR_VAL_TRM "hdr_ver=1" TST_STR_VAL_TRM "name=foo.txt"), 3, VINF_SUCCESS }
165};
166
167/**
168 * Tests parsing multiple stream blocks.
169 *
170 * Same parsing behavior as for the tests above apply.
171 */
172static struct
173{
174 /** Stream data. */
175 const char *pbData;
176 /** Size of stream data (in bytes). */
177 size_t cbData;
178 /** Number of data blocks retrieved. These are separated by "\0\0". */
179 uint32_t cBlocks;
180 /** Overall result when done parsing. */
181 int iResult;
182} const g_aTestStream[] =
183{
184 /* No blocks. */
185 { "", sizeof(""), 0, VINF_SUCCESS },
186 /* Empty block (no key/value pairs), will not be accounted. */
187 { TST_STR_STM_TRM,
188 TST_STR_BYTES(TST_STR_STM_TRM), 0, VINF_EOF },
189 /* Good stuff. */
190 { TST_STR_AND_BYTES(TST_STR_VAL_TRM "b1=b2" TST_STR_STM_TRM), 1, VINF_EOF },
191 { TST_STR_AND_BYTES("b3=b31" TST_STR_STM_TRM), 1, VINF_EOF },
192 { TST_STR_AND_BYTES("b4=b41" TST_STR_BLK_TRM "b51=b61" TST_STR_STM_TRM), 2, VINF_EOF },
193 { TST_STR_AND_BYTES("b5=b51" TST_STR_VAL_TRM "b61=b71" TST_STR_STM_TRM), 1, VINF_EOF }
194};
195
196/**
197 * Reads and parses the stream from a given file.
198 *
199 * @returns RTEXITCODE
200 * @param pszFile Absolute path to file to parse.
201 */
202static int tstReadFromFile(const char *pszFile)
203{
204 RTFILE fh;
205 int rc = RTFileOpen(&fh, pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
206 AssertRCReturn(rc, RTEXITCODE_FAILURE);
207
208 uint64_t cbFileSize;
209 rc = RTFileQuerySize(fh, &cbFileSize);
210 AssertRCReturn(rc, RTEXITCODE_FAILURE);
211
212 GuestToolboxStream stream;
213 GuestToolboxStreamBlock block;
214
215 size_t cPairs = 0;
216 size_t cBlocks = 0;
217
218 unsigned aToRead[] = { 256, 23, 13 };
219 unsigned i = 0;
220
221 uint64_t cbToRead = cbFileSize;
222
223 for (unsigned a = 0; a < 32; a++)
224 {
225 uint8_t buf[_64K];
226 do
227 {
228 size_t cbChunk = RT_MIN(cbToRead, i < RT_ELEMENTS(aToRead) ? aToRead[i++] : RTRandU64Ex(8, RT_MIN(sizeof(buf), 64)));
229 if (cbChunk > cbToRead)
230 cbChunk = cbToRead;
231 if (cbChunk)
232 {
233 RTTestIPrintf(RTTESTLVL_DEBUG, "Reading %zu bytes (of %zu left) ...\n", cbChunk, cbToRead);
234
235 size_t cbRead;
236 rc = RTFileRead(fh, &buf, cbChunk, &cbRead);
237 AssertRCBreak(rc);
238
239 if (!cbRead)
240 continue;
241
242 cbToRead -= cbRead;
243
244 rc = stream.AddData((BYTE *)buf, cbRead);
245 AssertRCBreak(rc);
246 }
247
248 rc = stream.ParseBlock(block);
249 Assert(rc != VERR_INVALID_PARAMETER);
250 RTTestIPrintf(RTTESTLVL_DEBUG, "Parsing ended with %Rrc\n", rc);
251 if (block.IsComplete())
252 {
253 /* Sanity checks; disable this if you parse anything else but fsinfo output from VBoxService toolbox. */
254 //Assert(block.GetString("name") != NULL);
255
256 cPairs += block.GetCount();
257 cBlocks = stream.GetBlocks();
258 block.Clear();
259 }
260 } while (VINF_SUCCESS == rc /* Might also be VINF_EOF when finished */);
261
262 RTTestIPrintf(RTTESTLVL_ALWAYS, "Total %zu blocks + %zu pairs\n", cBlocks, cPairs);
263
264 /* Reset. */
265 RTFileSeek(fh, 0, RTFILE_SEEK_BEGIN, NULL);
266 cbToRead = cbFileSize;
267 cPairs = 0;
268 cBlocks = 0;
269 block.Clear();
270 stream.Destroy();
271 }
272
273 int rc2 = RTFileClose(fh);
274 if (RT_SUCCESS(rc))
275 rc = rc2;
276
277 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
278}
279
280int main(int argc, char **argv)
281{
282 RTTEST hTest;
283 RTEXITCODE rcExit = RTTestInitAndCreate("tstParseBuffer", &hTest);
284 if (rcExit != RTEXITCODE_SUCCESS)
285 return rcExit;
286 RTTestBanner(hTest);
287
288#ifdef DEBUG
289 RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG;
290#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
291 fFlags |= RTLOGFLAGS_USECRLF;
292#endif
293 static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES;
294 int rc = RTLogCreate(&g_pLog, fFlags, "guest_control.e.l.l2.l3.f", NULL,
295 RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, RTLOGDEST_STDOUT, NULL /*"vkat-release.log"*/);
296 AssertRCReturn(rc, rc);
297 RTLogSetDefaultInstance(g_pLog);
298#endif
299
300 if (argc > 1)
301 return tstReadFromFile(argv[1]);
302
303 RTTestIPrintf(RTTESTLVL_DEBUG, "Initializing COM...\n");
304 HRESULT hrc = com::Initialize();
305 if (FAILED(hrc))
306 {
307 RTTestFailed(hTest, "Failed to initialize COM (%Rhrc)!\n", hrc);
308 return RTEXITCODE_FAILURE;
309 }
310
311 AssertCompile(TST_STR_BYTES("1") == 1);
312 AssertCompile(TST_STR_BYTES("sizecheck") == 9);
313 AssertCompile(TST_STR_BYTES("off=rab") == 7);
314 AssertCompile(TST_STR_BYTES("off=rab\0\0") == 9);
315
316 RTTestSub(hTest, "Blocks");
317
318 RTTestDisableAssertions(hTest);
319
320 for (unsigned iTest = 0; iTest < RT_ELEMENTS(g_aTestBlocks); iTest++)
321 {
322 RTTestIPrintf(RTTESTLVL_DEBUG, "=> Block test #%u:\n'%.*Rhxd\n", iTest, g_aTestBlocks[iTest].cbData, g_aTestBlocks[iTest].pbData);
323
324 GuestToolboxStream stream;
325 int iResult = stream.AddData((BYTE *)g_aTestBlocks[iTest].pbData, g_aTestBlocks[iTest].cbData);
326 if (RT_SUCCESS(iResult))
327 {
328 GuestToolboxStreamBlock curBlock;
329 iResult = stream.ParseBlock(curBlock);
330 if (iResult != g_aTestBlocks[iTest].iResult)
331 RTTestFailed(hTest, "Block #%u: Returned %Rrc, expected %Rrc\n", iTest, iResult, g_aTestBlocks[iTest].iResult);
332 else if (stream.GetOffset() != g_aTestBlocks[iTest].offAfter)
333 RTTestFailed(hTest, "Block #%u: Offset %zu wrong ('%#x'), expected %u ('%#x')\n",
334 iTest, stream.GetOffset(), g_aTestBlocks[iTest].pbData[stream.GetOffset()],
335 g_aTestBlocks[iTest].offAfter, g_aTestBlocks[iTest].pbData[g_aTestBlocks[iTest].offAfter]);
336 else if (iResult == VERR_MORE_DATA)
337 RTTestIPrintf(RTTESTLVL_DEBUG, "\tMore data (Offset: %zu)\n", stream.GetOffset());
338
339 if (RT_SUCCESS(iResult) || iResult == VERR_MORE_DATA)
340 if (curBlock.GetCount() != g_aTestBlocks[iTest].cMapElements)
341 RTTestFailed(hTest, "Block #%u: Map has %u elements, expected %u\n",
342 iTest, curBlock.GetCount(), g_aTestBlocks[iTest].cMapElements);
343
344 /* There is remaining data left in the buffer (which needs to be merged
345 * with a following buffer) -- print it. */
346 size_t off = stream.GetOffset();
347 size_t cbToWrite = g_aTestBlocks[iTest].cbData - off;
348 if (cbToWrite)
349 {
350 RTTestIPrintf(RTTESTLVL_DEBUG, "\tRemaining (%u):\n", cbToWrite);
351
352 /* How to properly get the current RTTESTLVL (aka IPRT_TEST_MAX_LEVEL) here?
353 * Hack alert: Using RTEnvGet for now. */
354 if (!RTStrICmp(RTEnvGet("IPRT_TEST_MAX_LEVEL"), "debug"))
355 RTStrmWriteEx(g_pStdOut, &g_aTestBlocks[iTest].pbData[off], cbToWrite - 1, NULL);
356 }
357
358 if (RTTestIErrorCount())
359 break;
360 }
361 }
362
363 RTTestSub(hTest, "Streams");
364
365 for (unsigned iTest = 0; iTest < RT_ELEMENTS(g_aTestStream); iTest++)
366 {
367 RTTestIPrintf(RTTESTLVL_DEBUG, "=> Stream test #%u\n%.*Rhxd\n",
368 iTest, g_aTestStream[iTest].cbData, g_aTestStream[iTest].pbData);
369
370 GuestToolboxStream stream;
371 int iResult = stream.AddData((BYTE*)g_aTestStream[iTest].pbData, g_aTestStream[iTest].cbData);
372 if (RT_SUCCESS(iResult))
373 {
374 uint32_t cBlocksComplete = 0;
375 uint8_t cSafety = 0;
376 do
377 {
378 GuestToolboxStreamBlock curBlock;
379 iResult = stream.ParseBlock(curBlock);
380 RTTestIPrintf(RTTESTLVL_DEBUG, "Stream #%u: Returned with %Rrc\n", iTest, iResult);
381 if (cSafety++ > 8)
382 break;
383 if (curBlock.IsComplete())
384 cBlocksComplete++;
385 } while (iResult != VINF_EOF);
386
387 if (iResult != g_aTestStream[iTest].iResult)
388 RTTestFailed(hTest, "Stream #%u: Returned %Rrc, expected %Rrc\n", iTest, iResult, g_aTestStream[iTest].iResult);
389 else if (cBlocksComplete != g_aTestStream[iTest].cBlocks)
390 RTTestFailed(hTest, "Stream #%u: Returned %u blocks, expected %u\n", iTest, cBlocksComplete, g_aTestStream[iTest].cBlocks);
391 }
392 else
393 RTTestFailed(hTest, "Stream #%u: Adding data failed with %Rrc\n", iTest, iResult);
394
395 if (RTTestIErrorCount())
396 break;
397 }
398
399 RTTestRestoreAssertions(hTest);
400
401 RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
402 com::Shutdown();
403
404 /*
405 * Summary.
406 */
407 return RTTestSummaryAndDestroy(hTest);
408}
409
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