1//
2// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7
8#include <boost/mysql/client_errc.hpp>
9#include <boost/mysql/error_code.hpp>
10
11#include <boost/mysql/impl/internal/sansio/message_reader.hpp>
12#include <boost/mysql/impl/internal/sansio/read_buffer.hpp>
13
14#include <boost/test/tools/interface.hpp>
15#include <boost/test/unit_test.hpp>
16
17#include <algorithm>
18#include <cstddef>
19#include <cstdint>
20#include <cstring>
21#include <vector>
22
23#include "test_common/assert_buffer_equals.hpp"
24#include "test_common/buffer_concat.hpp"
25#include "test_unit/create_frame.hpp"
26
27using namespace boost::mysql::detail;
28using namespace boost::mysql::test;
29using boost::span;
30using boost::mysql::client_errc;
31using boost::mysql::error_code;
32
33using u8vec = std::vector<std::uint8_t>;
34
35BOOST_AUTO_TEST_SUITE(test_message_reader)
36
37class reader_fixture
38{
39public:
40 message_reader reader;
41 std::uint8_t seqnum{42};
42
43 reader_fixture(std::vector<std::uint8_t> contents, std::size_t buffsize = 512)
44 : reader(buffsize, 64), // max frame size is 64
45 contents_(std::move(contents)),
46 buffer_first_(reader.internal_buffer().first())
47 {
48 }
49
50 void set_contents(u8vec value)
51 {
52 contents_ = std::move(value);
53 bytes_written_ = 0u;
54 }
55
56 // Reads bytes until reader.done() or all bytes in contents have been read.
57 // Resizes the buffer as required
58 void read_until_completion()
59 {
60 while (!reader.done() && remaining_bytes())
61 {
62 reader.prepare_buffer();
63 std::size_t bytes_to_copy = (std::min)(a: reader.buffer().size(), b: contents_.size() - bytes_written_);
64 read_bytes(num_bytes: bytes_to_copy);
65 }
66 BOOST_TEST(reader.done());
67 BOOST_TEST(remaining_bytes() == 0u);
68 }
69
70 // Simulates a read of num_bytes against the read buffer, then processes the result.
71 // Doesn't resize the buffer
72 void read_bytes(std::size_t num_bytes)
73 {
74 // Simulate a write against the buffer
75 if (num_bytes)
76 {
77 assert(num_bytes <= reader.buffer().size());
78 std::memcpy(dest: reader.buffer().data(), src: contents_.data() + bytes_written_, n: num_bytes);
79 bytes_written_ += num_bytes;
80 }
81
82 // Trigger the op
83 reader.resume(bytes_read: num_bytes);
84 }
85
86 span<const std::uint8_t> check_message(const std::vector<std::uint8_t>& expected)
87 {
88 BOOST_TEST_REQUIRE(reader.done());
89 BOOST_TEST_REQUIRE(reader.error() == error_code());
90 auto msg = reader.message();
91 BOOST_MYSQL_ASSERT_BUFFER_EQUALS(msg, expected);
92 return msg;
93 }
94
95 void record_buffer_first() noexcept { buffer_first_ = reader.internal_buffer().first(); }
96 void check_buffer_stability() { BOOST_TEST(reader.internal_buffer().first() == buffer_first_); }
97 std::size_t buffsize() const noexcept { return reader.internal_buffer().size(); }
98
99private:
100 std::vector<std::uint8_t> contents_;
101 std::size_t bytes_written_{0};
102 const std::uint8_t* buffer_first_;
103
104 std::size_t remaining_bytes() const noexcept { return contents_.size() - bytes_written_; }
105};
106
107// Parsing algorithm. Without buffer relocations or short reads
108BOOST_AUTO_TEST_CASE(parsing_algorithm_success)
109{
110 struct
111 {
112 const char* name;
113 u8vec input;
114 u8vec expected_msg;
115 std::uint8_t expected_seqnum;
116 } test_cases[] = {
117 {.name: "empty_message", .input: create_empty_frame(seqnum: 42), .expected_msg: {}, .expected_seqnum: 43},
118 {.name: "one_frame", .input: create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}), .expected_msg: {0x01, 0x02, 0x03}, .expected_seqnum: 43},
119 {.name: "one_frame_max_size",
120 .input: buffer_builder().add(value: create_frame(seqnum: 42, body: u8vec(64, 0x04))).add(value: create_empty_frame(seqnum: 43)).build(),
121 .expected_msg: u8vec(64, 0x04),
122 .expected_seqnum: 44},
123 {.name: "two_frames",
124 .input: buffer_builder().add(value: create_frame(seqnum: 42, body: u8vec(64, 0x04))).add(value: create_frame(seqnum: 43, body: {0x05, 0x06})).build(),
125 .expected_msg: concat_copy(lhs: u8vec(64, 0x04), rhs: {0x05, 0x06}),
126 .expected_seqnum: 44},
127 {.name: "two_frames_max_size",
128 .input: buffer_builder()
129 .add(value: create_frame(seqnum: 42, body: u8vec(64, 0x04)))
130 .add(value: create_frame(seqnum: 43, body: u8vec(64, 0x05)))
131 .add(value: create_empty_frame(seqnum: 44))
132 .build(),
133 .expected_msg: concat_copy(lhs: u8vec(64, 0x04), rhs: u8vec(64, 0x05)),
134 .expected_seqnum: 45},
135 {.name: "three_frames",
136 .input: buffer_builder()
137 .add(value: create_frame(seqnum: 42, body: u8vec(64, 0x04)))
138 .add(value: create_frame(seqnum: 43, body: u8vec(64, 0x05)))
139 .add(value: create_frame(seqnum: 44, body: {0x0a}))
140 .build(),
141 .expected_msg: buffer_builder().add(value: u8vec(64, 0x04)).add(value: u8vec(64, 0x05)).add(value: {0x0a}).build(),
142 .expected_seqnum: 45},
143 };
144
145 for (auto& tc : test_cases)
146 {
147 BOOST_TEST_CONTEXT(tc.name)
148 {
149 // Setup
150 reader_fixture fix(tc.input);
151 fix.reader.prepare_read(sequence_number&: fix.seqnum);
152 BOOST_TEST(!fix.reader.done());
153
154 // Receive the message
155 fix.read_bytes(num_bytes: tc.input.size());
156
157 // Check
158 fix.check_message(expected: tc.expected_msg);
159 BOOST_TEST(fix.seqnum == tc.expected_seqnum);
160
161 // Buffer didn't reallocate
162 fix.check_buffer_stability();
163 }
164 }
165}
166
167BOOST_AUTO_TEST_CASE(seqnum_overflow)
168{
169 // message to be parsed
170 reader_fixture fix(
171 buffer_builder()
172 .add(value: create_frame(seqnum: 255, body: std::vector<std::uint8_t>(64, 0x04)))
173 .add(value: create_frame(seqnum: 0, body: {0x05, 0x06, 0x07}))
174 .build(),
175 64 + 16
176 );
177 fix.seqnum = 255;
178
179 // Setup
180 fix.reader.prepare_read(sequence_number&: fix.seqnum);
181 BOOST_TEST(!fix.reader.done());
182
183 // all in one
184 fix.read_bytes(num_bytes: 64 + 4 * 2 + 3);
185 fix.check_message(expected: concat_copy(lhs: std::vector<std::uint8_t>(64, 0x04), rhs: {0x05, 0x06, 0x07}));
186 BOOST_TEST(fix.seqnum == 1u);
187
188 // Buffer didn't reallocate
189 fix.check_buffer_stability();
190}
191
192BOOST_AUTO_TEST_CASE(seqnum_mismatch)
193{
194 struct
195 {
196 const char* name;
197 u8vec input;
198 } test_cases[] = {
199 {.name: "1st_frame", .input: create_frame(seqnum: 1, body: {0x01, 0x02})},
200 {.name: "2nd_frame", .input: concat_copy(lhs: create_frame(seqnum: 42, body: u8vec(64, 0x04)), rhs: create_frame(seqnum: 44, body: {0x01}))},
201 };
202
203 for (const auto& tc : test_cases)
204 {
205 BOOST_TEST_CONTEXT(tc.name)
206 {
207 reader_fixture fix(tc.input);
208 fix.reader.prepare_read(sequence_number&: fix.seqnum);
209 fix.read_bytes(num_bytes: tc.input.size());
210 BOOST_TEST(fix.reader.done());
211 BOOST_TEST(fix.reader.error() == error_code(client_errc::sequence_number_mismatch));
212 }
213 }
214}
215
216// Long read: we received two messages at once.
217// We don't consume the next message while parsing the first one
218// We don't get rid of the first message while there's space for the second one
219BOOST_AUTO_TEST_CASE(long_read)
220{
221 // message to be parsed
222 std::vector<std::uint8_t> first_msg_body{0x01, 0x02, 0x03};
223 std::vector<std::uint8_t> second_msg_body{0x04, 0x05, 0x06, 0x07};
224 reader_fixture fix(concat_copy(lhs: create_frame(seqnum: 42, body: first_msg_body), rhs: create_frame(seqnum: 2, body: second_msg_body)));
225
226 // Prepare first read
227 fix.reader.prepare_read(sequence_number&: fix.seqnum);
228 BOOST_TEST(!fix.reader.done());
229
230 // The read yields two messages at once
231 fix.read_bytes(num_bytes: 15);
232 fix.check_message(expected: first_msg_body);
233 BOOST_TEST(fix.seqnum == 43u);
234
235 // We can read the 2nd message, too
236 std::uint8_t seqnum2 = 2u;
237 fix.reader.prepare_read(sequence_number&: seqnum2);
238 fix.check_message(expected: second_msg_body);
239 BOOST_TEST(fix.seqnum == 43u); // old seqnum not updated
240 BOOST_TEST(seqnum2 == 3u); // new seqnum updated
241
242 // Buffer shouldn't reallocate
243 fix.check_buffer_stability();
244}
245
246// Short reads
247BOOST_AUTO_TEST_CASE(short_reads_multiple)
248{
249 // message to be parsed
250 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
251 fix.reader.prepare_read(sequence_number&: fix.seqnum);
252 BOOST_TEST(!fix.reader.done());
253
254 // 1 byte in the header received
255 fix.read_bytes(num_bytes: 1);
256 BOOST_TEST(!fix.reader.done());
257
258 // Another 2 bytes received
259 fix.read_bytes(num_bytes: 2);
260 BOOST_TEST(!fix.reader.done());
261
262 // Header fully received
263 fix.read_bytes(num_bytes: 1);
264 BOOST_TEST(!fix.reader.done());
265
266 // 1 byte in body received
267 fix.read_bytes(num_bytes: 1);
268 BOOST_TEST(!fix.reader.done());
269
270 // body fully received
271 fix.read_bytes(num_bytes: 2);
272 fix.check_message(expected: {0x01, 0x02, 0x03});
273 BOOST_TEST(fix.seqnum == 43u);
274
275 // Buffer shouldn't reallocate
276 fix.check_buffer_stability();
277}
278
279BOOST_AUTO_TEST_CASE(short_reads_header_size)
280{
281 // message to be parsed
282 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
283 fix.reader.prepare_read(sequence_number&: fix.seqnum);
284 BOOST_TEST(!fix.reader.done());
285
286 // Full header received
287 fix.read_bytes(num_bytes: 4);
288 BOOST_TEST(!fix.reader.done());
289
290 // Full body received
291 fix.read_bytes(num_bytes: 3);
292 fix.check_message(expected: {0x01, 0x02, 0x03});
293 BOOST_TEST(fix.seqnum == 43u);
294
295 // Buffer didn't reallocate
296 fix.check_buffer_stability();
297}
298
299BOOST_AUTO_TEST_CASE(short_reads_two_frames)
300{
301 // message to be parsed
302 reader_fixture fix(
303 buffer_builder()
304 .add(value: create_frame(seqnum: 42, body: std::vector<std::uint8_t>(64, 0x04)))
305 .add(value: create_frame(seqnum: 43, body: {0x05, 0x06, 0x07}))
306 .build(),
307 64 + 16
308 );
309 auto expected_message = concat_copy(lhs: std::vector<std::uint8_t>(64, 0x04), rhs: {0x05, 0x06, 0x07});
310
311 // Setup
312 fix.reader.prepare_read(sequence_number&: fix.seqnum);
313
314 // part of header 1
315 fix.read_bytes(num_bytes: 3);
316 BOOST_TEST(!fix.reader.done());
317
318 // header 1 full
319 fix.read_bytes(num_bytes: 1);
320 BOOST_TEST(!fix.reader.done());
321
322 // part of body 1
323 fix.read_bytes(num_bytes: 64 - 8);
324 BOOST_TEST(!fix.reader.done());
325
326 // rest of body 1
327 fix.read_bytes(num_bytes: 8);
328 BOOST_TEST(!fix.reader.done());
329
330 // part of header 2
331 fix.read_bytes(num_bytes: 1);
332 BOOST_TEST(!fix.reader.done());
333
334 // another part of header 2
335 fix.read_bytes(num_bytes: 2);
336 BOOST_TEST(!fix.reader.done());
337
338 // rest of header 2 and part of body 1
339 fix.read_bytes(num_bytes: 2);
340 BOOST_TEST(!fix.reader.done());
341
342 // another part of body 2
343 fix.read_bytes(num_bytes: 1);
344 BOOST_TEST(!fix.reader.done());
345
346 // remaining of body 2
347 fix.read_bytes(num_bytes: 1);
348 fix.check_message(expected: expected_message);
349 BOOST_TEST(fix.seqnum == 44u);
350
351 // Buffer shouldn't reallocate
352 fix.check_buffer_stability();
353}
354
355// Buffer resizing
356BOOST_AUTO_TEST_CASE(buffer_resizing_not_enough_space)
357{
358 // Setup
359 reader_fixture fix(create_frame(seqnum: 42, body: u8vec(50, 0x04)), 0);
360 BOOST_TEST(fix.buffsize() == 0u);
361
362 // Prepare read. The buffer hasn't resized.
363 fix.reader.prepare_read(sequence_number&: fix.seqnum);
364 BOOST_TEST(!fix.reader.done());
365 BOOST_TEST(fix.buffsize() == 0u);
366
367 // Resize the buffer
368 fix.reader.prepare_buffer();
369 fix.record_buffer_first();
370 BOOST_TEST(fix.buffsize() >= 4u);
371 BOOST_TEST(fix.buffsize() < 50u);
372
373 // Read the header. The buffer didn't reallocate
374 fix.read_bytes(num_bytes: 4);
375 BOOST_TEST(!fix.reader.done());
376 fix.check_buffer_stability();
377
378 // Resize the buffer again
379 fix.reader.prepare_buffer();
380 fix.record_buffer_first();
381 BOOST_TEST(fix.buffsize() >= 50u);
382
383 // Finish reading
384 fix.read_bytes(num_bytes: 50);
385 fix.check_message(expected: u8vec(50, 0x04));
386 BOOST_TEST(fix.seqnum == 43u);
387}
388
389BOOST_AUTO_TEST_CASE(buffer_resizing_old_messages_removed)
390{
391 // prepare_buffer removes old messages
392 // so the buffer doesn't grow indefinitely
393
394 // Setup
395 reader_fixture fix(create_frame(seqnum: 42, body: u8vec(60, 0x04)), 0);
396
397 // Parse an entire message, to make space in the buffer
398 fix.reader.prepare_read(sequence_number&: fix.seqnum);
399 fix.read_until_completion();
400 fix.check_message(expected: u8vec(60, 0x04));
401
402 // Record size, as this should not increase
403 const std::size_t old_size = fix.buffsize();
404 BOOST_TEST(old_size >= 60u);
405
406 // Parse new messages
407 for (std::uint8_t i = 0u; i < 100u; ++i)
408 {
409 // Setup
410 u8vec msg_body(50, i);
411 fix.seqnum = i;
412 fix.set_contents(create_frame(seqnum: i, body: msg_body));
413
414 // Prepare read
415 fix.reader.prepare_read(sequence_number&: fix.seqnum);
416
417 // Read the message into the buffer and trigger the op until completion.
418 // This will call prepare_buffer() internally
419 fix.read_until_completion();
420
421 // Check results
422 BOOST_TEST_REQUIRE(fix.reader.error() == error_code());
423 BOOST_MYSQL_ASSERT_BUFFER_EQUALS(fix.reader.message(), msg_body);
424 }
425
426 // Buffer size should be the same
427 BOOST_TEST(fix.buffsize() == old_size);
428}
429
430// Keep parsing state
431BOOST_AUTO_TEST_CASE(keep_state_continuation)
432{
433 // Setup. We use a multiframe message to verify that we update the sequence number reference correctly
434 u8vec msg1_body{0x01, 0x02, 0x03};
435 u8vec msg2_body(65, 0x04);
436 reader_fixture fix(
437 buffer_builder()
438 .add(value: create_frame(seqnum: 42, body: msg1_body))
439 .add(value: create_frame(seqnum: 43, body: u8vec(64, 0x04)))
440 .add(value: create_frame(seqnum: 44, body: {0x04}))
441 .build(),
442 16
443 );
444
445 // Read the first message and part of the second
446 fix.reader.prepare_read(sequence_number&: fix.seqnum);
447 fix.read_bytes(num_bytes: 16);
448 auto msg = fix.check_message(expected: {0x01, 0x02, 0x03});
449 BOOST_TEST(fix.seqnum == 43u);
450
451 // Prepare the second read. We don't have enough bytes or buffer space
452 fix.reader.prepare_read(sequence_number&: fix.seqnum);
453 BOOST_TEST(!fix.reader.done());
454 fix.check_buffer_stability(); // Didn't reallocate
455 BOOST_MYSQL_ASSERT_BUFFER_EQUALS(msg, msg1_body); // Old message still valid
456 BOOST_TEST(fix.seqnum == 44u); // Updated to the last received seqnum
457
458 // Prepare a read as a continuation. This will not throw away the parsing state
459 std::uint8_t new_seqnum{fix.seqnum};
460 fix.reader.prepare_read(sequence_number&: new_seqnum, keep_state: true);
461 fix.read_until_completion();
462 fix.check_message(expected: msg2_body);
463 BOOST_TEST(fix.seqnum == 44u); // Old seqnum not updated
464 BOOST_TEST(new_seqnum == 45u); // New seqnum updated
465}
466
467BOOST_AUTO_TEST_CASE(keep_state_done)
468{
469 // Passing keep_state=true won't have effect if the operation is already done
470 reader_fixture fix(
471 buffer_builder().add(value: create_frame(seqnum: 42, body: {0x01, 0x02, 0x03})).add(value: create_frame(seqnum: 43, body: {0x04, 0x05})).build()
472 );
473
474 // Read the first message
475 fix.reader.prepare_read(sequence_number&: fix.seqnum);
476 fix.read_bytes(num_bytes: 7);
477 fix.check_message(expected: {0x01, 0x02, 0x03});
478 BOOST_TEST(fix.seqnum == 43u);
479
480 // Prepare a read as a continuation. As the operation is done, this will reset parsing state
481 fix.reader.prepare_read(sequence_number&: fix.seqnum, keep_state: true);
482 BOOST_TEST(!fix.reader.done());
483 fix.read_bytes(num_bytes: 6);
484 fix.check_message(expected: {0x04, 0x05});
485 BOOST_TEST(fix.seqnum == 44u);
486}
487
488BOOST_AUTO_TEST_CASE(keep_state_initial)
489{
490 // Passing keep_state=true with a reader that hasn't been used works
491 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
492 fix.reader.prepare_read(sequence_number&: fix.seqnum, keep_state: true);
493 fix.read_bytes(num_bytes: 7);
494 fix.check_message(expected: {0x01, 0x02, 0x03});
495 BOOST_TEST(fix.seqnum == 43u);
496}
497
498// Resetting
499BOOST_AUTO_TEST_CASE(reset_done)
500{
501 // Read a message until completion
502 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
503 fix.reader.prepare_read(sequence_number&: fix.seqnum);
504 fix.read_until_completion();
505
506 // Reset
507 fix.reader.reset();
508
509 // A new message can be read now
510 fix.set_contents(create_frame(seqnum: 20, body: {0x09, 0x0a}));
511 fix.seqnum = 20;
512 fix.reader.prepare_read(sequence_number&: fix.seqnum);
513 fix.read_until_completion();
514 fix.check_message(expected: {0x09, 0x0a});
515 fix.check_buffer_stability(); // No reallocation happened
516 BOOST_TEST(fix.seqnum == 21u);
517}
518
519BOOST_AUTO_TEST_CASE(reset_message_half_read)
520{
521 // Read part of a message
522 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
523 fix.reader.prepare_read(sequence_number&: fix.seqnum);
524 fix.read_bytes(num_bytes: 3);
525
526 // Reset
527 fix.reader.reset();
528
529 // A new message can be read now
530 fix.set_contents(create_frame(seqnum: 20, body: {0x09, 0x0a}));
531 fix.seqnum = 20;
532 fix.reader.prepare_read(sequence_number&: fix.seqnum);
533 fix.read_until_completion();
534 fix.check_message(expected: {0x09, 0x0a});
535 fix.check_buffer_stability(); // No reallocation happened
536 BOOST_TEST(fix.seqnum == 21u);
537}
538
539BOOST_AUTO_TEST_CASE(reset_keep_state_true)
540{
541 // Read part of a message
542 reader_fixture fix(create_frame(seqnum: 42, body: {0x01, 0x02, 0x03}));
543 fix.reader.prepare_read(sequence_number&: fix.seqnum, keep_state: true);
544 fix.read_bytes(num_bytes: 3);
545
546 // Reset
547 fix.reader.reset();
548
549 // A new message can be read now
550 fix.set_contents(create_frame(seqnum: 20, body: {0x09, 0x0a}));
551 fix.seqnum = 20;
552 fix.reader.prepare_read(sequence_number&: fix.seqnum);
553 fix.read_until_completion();
554 fix.check_message(expected: {0x09, 0x0a});
555 fix.check_buffer_stability(); // No reallocation happened
556 BOOST_TEST(fix.seqnum == 21u);
557}
558
559BOOST_AUTO_TEST_SUITE_END()
560

source code of boost/libs/mysql/test/unit/test/sansio/message_reader.cpp