message_parser tests 1

This commit is contained in:
Ruben Perez 2022-09-19 16:28:08 +02:00
parent 6e5d1c21a7
commit f06d0cd775
9 changed files with 294 additions and 28 deletions

View File

@ -40,7 +40,7 @@ inline void boost::mysql::detail::message_parser::parse_message(
// Deserialize the header
packet_header header;
deserialization_context ctx (
boost::asio::buffer(buff.pending_first(), HEADER_SIZE),
boost::asio::buffer(buff.pending_first() - HEADER_SIZE, HEADER_SIZE),
capabilities(0) // unaffected by capabilities
);
errc err = deserialize(ctx, header);
@ -50,7 +50,6 @@ inline void boost::mysql::detail::message_parser::parse_message(
// Process the sequence number
if (state_.is_first_frame)
{
state_.is_first_frame = false;
state_.seqnum_first = header.sequence_number;
state_.seqnum_last = header.sequence_number;
}
@ -79,6 +78,7 @@ inline void boost::mysql::detail::message_parser::parse_message(
{
buff.remove_current_message_last(HEADER_SIZE);
}
state_.is_first_frame = false;
state_.reading_header = false;
}

View File

@ -24,7 +24,7 @@ class message_parser
bool is_first_frame {true};
std::uint8_t seqnum_first {};
std::uint8_t seqnum_last {};
bool reading_header {false};
bool reading_header {true};
std::size_t remaining_bytes {0};
bool more_frames_follow {false};
bool has_seqnum_mismatch {false};
@ -62,7 +62,9 @@ public:
// Attempts to process a message from buff and puts it into msg.
// If a message is read, returns true, and msg.message_first and msg.message_last
// will contain offsets into buff's reserved area. Otherwise, required_size will contain
// the number of bytes needed to complete the message part we're parsing.
// the number of bytes needed to complete the message part we're parsing.
// Doesn't cause buffer reallocations, and doesn't change the contents
// of buff's reserved area.
inline void parse_message(read_buffer& buff, result& res) noexcept;
};

View File

@ -32,6 +32,11 @@ public:
inline boost::asio::const_buffer get_next_message(std::uint8_t& seqnum, error_code& ec) noexcept;
// Reads some messages from stream, until there is at least one
// or an error happens. On success, has_message() returns true
// and get_next_message() returns the parsed message.
// May relocate the buffer, modifying buffer_first(). If !keep_messages,
// the reserved area bytes will be removed before the actual read; otherwise,
// they won't be touched (but may be reallocated).
template <class Stream>
void read_some(Stream& stream, error_code& ec, bool keep_messages = false);

View File

@ -29,6 +29,7 @@ target_link_libraries(
add_executable(
boost_mysql_unittests
unit/detail/channel/read_buffer.cpp
unit/detail/channel/message_parser.cpp
# unit/detail/auth/auth_calculator.cpp
# unit/detail/auxiliar/static_string.cpp

View File

@ -0,0 +1,43 @@
//
// Copyright (c) 2019-2022 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_MYSQL_TEST_COMMON_ASSERT_BUFFER_EQUALS_HPP
#define BOOST_MYSQL_TEST_COMMON_ASSERT_BUFFER_EQUALS_HPP
#include <boost/asio/buffer.hpp>
#include <boost/test/unit_test.hpp>
#include <cstring>
namespace boost {
namespace mysql {
namespace test {
struct buffer_printer
{
boost::asio::const_buffer buff;
};
inline std::ostream& operator<<(std::ostream& os, buffer_printer buff)
{
os << "{ ";
for (std::size_t i = 0; i < buff.buff.size(); ++i)
{
os << static_cast<int>(static_cast<const std::uint8_t*>(buff.buff.data())[i]) << ", ";
}
return os << "}";
}
} // test
} // mysql
} // boost
#define BOOST_MYSQL_ASSERT_BUFFER_EQUALS(b1, b2) \
BOOST_TEST( \
::std::memcmp(b1.data(), b2.data(), b1.size()) == 0, \
#b1 " != " #b2 ": " << ::boost::mysql::test::buffer_printer{b1} << " != " << ::boost::mysql::test::buffer_printer{b2})
#endif

View File

@ -0,0 +1,161 @@
//
// Copyright (c) 2019-2022 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/detail/protocol/capabilities.hpp>
#include <boost/mysql/detail/protocol/serialization.hpp>
#include <boost/mysql/detail/protocol/serialization_context.hpp>
#include <boost/mysql/detail/channel/read_buffer.hpp>
#include <boost/mysql/detail/channel/message_parser.hpp>
#include <boost/mysql/detail/protocol/common_messages.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "assert_buffer_equals.hpp"
using boost::mysql::detail::message_parser;
using boost::mysql::detail::read_buffer;
using boost::mysql::detail::packet_header;
using boost::mysql::detail::int3;
/**
process_message
fragmented msg 1
header pìece
header piece
completes header
body piece
body piece
completes body
fragmented msg 2
header
body
fragmented msg 3
header + body part
end of body + next header + next body part
full message: header + body
several messages, then fragmented one
header 1 + body 1 + header 2 + body 2 + header 3 + body fragment 3
several messages
header 1 + body 1 + header 2 + body 2
2-frame message
header 1 + body 1 part
body 1 part
body 1 part + header 2 + body 2 part
body 2 part
3-frame message
header 1 + body 1 part
body 1 part
body 1 part + header 2 + body 2 part
body 2 part
body 2 part + header 3 + body 3
2-frame message in single read (not possible?)
header 1 + body 1 + header 2 + body 2
2-frame message with fragmented header
header 1 piece
header 1 piece + body 1 piece
body 1 piece + header 2 piece
coming from an already processed message (can we get rid of this?)
coming from an error (can we get rid of this?)
with reserved area
2-frame message with mismatched seqnums
2 different frames with "mismatched" seqnums
*
*/
namespace
{
class parser_test
{
message_parser parser_;
read_buffer buff_;
std::vector<std::uint8_t> contents_;
std::size_t bytes_written_ {0};
const std::uint8_t* buffer_first_;
public:
parser_test(std::vector<std::uint8_t> contents, std::size_t buffsize = 512) :
buff_(buffsize), contents_(std::move(contents)), buffer_first_(buff_.first()) {}
read_buffer& buffer() noexcept { return buff_; }
message_parser::result parse_bytes(std::size_t num_bytes)
{
message_parser::result res;
std::memcpy(buff_.free_first(), contents_.data() + bytes_written_, num_bytes);
bytes_written_ += num_bytes;
buff_.move_to_pending(num_bytes);
parser_.parse_message(buff_, res);
return res;
}
void check_message(const std::vector<std::uint8_t>& contents)
{
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(
boost::asio::buffer(buff_.current_message_first() - contents.size(), contents.size()),
boost::asio::buffer(contents)
);
}
void check_buffer_stability()
{
BOOST_TEST(buff_.first() == buffer_first_);
}
};
std::vector<std::uint8_t> create_message(std::uint8_t seqnum, std::vector<std::uint8_t> body)
{
std::uint32_t body_size = body.size();
packet_header header { int3{body_size}, seqnum };
body.resize(body_size + 4);
std::memmove(body.data() + 4, body.data(), body_size);
boost::mysql::detail::serialization_context ctx (boost::mysql::detail::capabilities(), body.data());
boost::mysql::detail::serialize(ctx, header);
return body;
}
BOOST_AUTO_TEST_SUITE(message_parser_parse_message)
BOOST_AUTO_TEST_CASE(fragmented_header_and_body)
{
// message to be parsed
parser_test fixture (create_message(0, { 0x01, 0x02, 0x03 }));
// 1 byte in the header received
auto res = fixture.parse_bytes(1);
BOOST_TEST(!res.has_message);
BOOST_TEST(res.required_size == 3);
// Another 2 bytes received
res = fixture.parse_bytes(2);
BOOST_TEST(!res.has_message);
BOOST_TEST(res.required_size == 1);
// Header fully received
res = fixture.parse_bytes(1);
BOOST_TEST(!res.has_message);
BOOST_TEST(res.required_size == 3);
// 1 byte in body received
res = fixture.parse_bytes(1);
BOOST_TEST(!res.has_message);
BOOST_TEST(res.required_size == 2);
// body fully received (single frame messages keep header as an optimization)
res = fixture.parse_bytes(2);
fixture.check_message({ 0x01, 0x02, 0x03 });
BOOST_TEST(res.has_message);
BOOST_TEST(res.message.size == 3);
BOOST_TEST(res.message.seqnum_first == 0);
BOOST_TEST(res.message.seqnum_last == 0);
BOOST_TEST(!res.message.has_seqnum_mismatch);
// Buffer did not reallocate
fixture.check_buffer_stability();
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -0,0 +1,73 @@
//
// Copyright (c) 2019-2022 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <boost/mysql/detail/channel/message_reader.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
/**
process_message
fragmented msg 2
header
body
fragmented msg 3
header + body part
end of body + next header + next body part
full message: header + body
several messages, then fragmented one
header 1 + body 1 + header 2 + body 2 + header 3 + body fragment 3
several messages
header 1 + body 1 + header 2 + body 2
2-frame message
header 1 + body 1 part
body 1 part
body 1 part + header 2 + body 2 part
body 2 part
3-frame message
header 1 + body 1 part
body 1 part
body 1 part + header 2 + body 2 part
body 2 part
body 2 part + header 3 + body 3
2-frame message in single read (not possible?)
header 1 + body 1 + header 2 + body 2
2-frame message with fragmented header
header 1 piece
header 1 piece + body 1 piece
body 1 piece + header 2 piece
coming from an already processed message (can we get rid of this?)
coming from an error (can we get rid of this?)
with reserved area
2-frame message with mismatched seqnums
2 different frames with "mismatched" seqnums
next_message
passed number seqnum mismatch
intermediate frame seqnum mismatch
OK, there is next message
OK, there isn't next message
read_some
has already a message
message that fits in the buffer
message that fits in the buffer, but segmented in two
message that doesn't fit in the buffer (segmented)
there is a previous message, keep_messages = false
there is a previous message, keep_messages = true
error in a read
read_one
*
*/
namespace
{
}

View File

@ -15,28 +15,14 @@
#include <cstring>
#include <ostream>
#include <vector>
#include "assert_buffer_equals.hpp"
using boost::mysql::detail::read_buffer;
using boost::asio::const_buffer;
using boost::asio::buffer;
namespace
{
struct buffer_printer
{
const_buffer buff;
};
std::ostream& operator<<(std::ostream& os, buffer_printer buff)
{
os << "{ ";
for (std::size_t i = 0; i < buff.buff.size(); ++i)
{
os << static_cast<int>(static_cast<const std::uint8_t*>(buff.buff.data())[i]) << ", ";
}
return os << "}";
}
// Records the buffer first pointer and size to verify the buffer
// didn't do any re-allocation
@ -61,11 +47,6 @@ public:
} // anon namespace
#define BOOST_MYSQL_BUFF_TEST(b1, b2) \
BOOST_TEST( \
std::memcmp(b1.data(), b2.data(), b1.size()) == 0, \
#b1 " != " #b2 ": " << buffer_printer{b1} << " != " << buffer_printer{b2})
static void check_buffer(
read_buffer& buff,
@ -125,9 +106,9 @@ static void check_buffer(
buff.size() - free_offset
);
BOOST_MYSQL_BUFF_TEST(buff.reserved_area(), buffer(reserved));
BOOST_MYSQL_BUFF_TEST(buff.current_message(), buffer(current_message));
BOOST_MYSQL_BUFF_TEST(buff.pending_area(), buffer(pending));
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff.reserved_area(), buffer(reserved));
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff.current_message(), buffer(current_message));
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(buff.pending_area(), buffer(pending));
}
static void check_empty_buffer(read_buffer& buff)

View File

@ -14,7 +14,7 @@ from collections import namedtuple
# Script to get file headers (copyright notices
# and include guards) okay and up to date
REPO_BASE = path.abspath(path.join(path.dirname(__file__), '..', '..'))
REPO_BASE = path.abspath(path.join(path.dirname(path.realpath(__file__)), '..', '..'))
BASE_FOLDERS = [
'cmake',
'doc',