From db1e3005bbbbe78175acea188258254c76ae1346 Mon Sep 17 00:00:00 2001 From: ruben Date: Sat, 21 Mar 2020 17:12:53 +0000 Subject: [PATCH] Converted serialization to a trait-based system --- TODO.txt | 7 +- .../mysql/detail/protocol/common_messages.hpp | 18 +- .../detail/protocol/handshake_messages.hpp | 28 +- .../detail/protocol/impl/common_messages.ipp | 14 +- .../protocol/impl/handshake_messages.ipp | 25 +- .../impl/prepared_statement_messages.hpp | 19 +- .../detail/protocol/impl/serialization.hpp | 310 +++++++++++ .../detail/protocol/impl/serialization.ipp | 132 +++-- .../protocol/prepared_statement_messages.hpp | 25 +- .../mysql/detail/protocol/query_messages.hpp | 3 +- .../mysql/detail/protocol/serialization.hpp | 481 +++++++----------- test/common/serialization_test_common.hpp | 8 +- test/unit/detail/protocol/serialization.cpp | 4 + 13 files changed, 684 insertions(+), 390 deletions(-) create mode 100644 include/boost/mysql/detail/protocol/impl/serialization.hpp diff --git a/TODO.txt b/TODO.txt index 3a882306..2e83af81 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,8 +1,13 @@ Name change + Get rid of get_struct_fields + Put serialization specializations together with the messages + Split serialization tests according to file structure + Split impls to impl header + Get rid of contexts files + Transform tags into an enum serialization_test_common Should be in unit/ Should not be header only - Should not require including all messages Should have things in namespace test, not detail Split serialization tests into chunks following file structure Try to make string_fixed just an array of chars instead of a value_holder diff --git a/include/boost/mysql/detail/protocol/common_messages.hpp b/include/boost/mysql/detail/protocol/common_messages.hpp index 435d4aac..6f4cb65e 100644 --- a/include/boost/mysql/detail/protocol/common_messages.hpp +++ b/include/boost/mysql/detail/protocol/common_messages.hpp @@ -1,11 +1,9 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_COMMON_MESSAGES_HPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_COMMON_MESSAGES_HPP_ -#include "boost/mysql/detail/protocol/deserialization_context.hpp" -#include "boost/mysql/detail/auxiliar/get_struct_fields.hpp" +#include "boost/mysql/detail/protocol/serialization.hpp" #include "boost/mysql/detail/protocol/constants.hpp" #include "boost/mysql/collation.hpp" -#include "boost/mysql/error.hpp" #include namespace boost { @@ -103,8 +101,18 @@ struct get_struct_fields ); }; -inline errc deserialize(ok_packet& output, deserialization_context& ctx) noexcept; -inline errc deserialize(column_definition_packet& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline errc deserialize_(ok_packet& output, deserialization_context& ctx) noexcept; +}; + +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline errc deserialize_(column_definition_packet& output, deserialization_context& ctx) noexcept; +}; + inline error_code process_error_packet(deserialization_context& ctx, error_info& info); } // detail diff --git a/include/boost/mysql/detail/protocol/handshake_messages.hpp b/include/boost/mysql/detail/protocol/handshake_messages.hpp index 74aa0529..a00613fd 100644 --- a/include/boost/mysql/detail/protocol/handshake_messages.hpp +++ b/include/boost/mysql/detail/protocol/handshake_messages.hpp @@ -1,11 +1,7 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_HANDSHAKE_MESSAGES_HPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_HANDSHAKE_MESSAGES_HPP_ -#include "boost/mysql/detail/protocol/serialization_context.hpp" -#include "boost/mysql/detail/protocol/deserialization_context.hpp" -#include "boost/mysql/detail/protocol/protocol_types.hpp" -#include "boost/mysql/detail/auxiliar/get_struct_fields.hpp" -#include "boost/mysql/error.hpp" +#include "boost/mysql/detail/protocol/serialization.hpp" namespace boost { namespace mysql { @@ -94,10 +90,24 @@ struct get_struct_fields ); }; -inline errc deserialize(handshake_packet& output, deserialization_context& ctx) noexcept; -inline std::size_t get_size(const handshake_response_packet& value, const serialization_context& ctx) noexcept; -inline void serialize(const handshake_response_packet& value, serialization_context& ctx) noexcept; -inline errc deserialize(auth_switch_request_packet& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline errc deserialize_(handshake_packet& output, deserialization_context& ctx) noexcept; +}; + +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline std::size_t get_size_(const handshake_response_packet& value, const serialization_context& ctx) noexcept; + static inline void serialize_(const handshake_response_packet& value, serialization_context& ctx) noexcept; +}; + +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline errc deserialize_(auth_switch_request_packet& output, deserialization_context& ctx) noexcept; +}; } // detail diff --git a/include/boost/mysql/detail/protocol/impl/common_messages.ipp b/include/boost/mysql/detail/protocol/impl/common_messages.ipp index 4e5833de..c3f01962 100644 --- a/include/boost/mysql/detail/protocol/impl/common_messages.ipp +++ b/include/boost/mysql/detail/protocol/impl/common_messages.ipp @@ -1,9 +1,11 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_COMMON_MESSAGES_IPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_COMMON_MESSAGES_IPP_ -#include "boost/mysql/detail/protocol/serialization.hpp" - -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::ok_packet, + boost::mysql::detail::struct_tag +>::deserialize_( ok_packet& output, deserialization_context& ctx ) noexcept @@ -24,7 +26,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( } } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::column_definition_packet, + boost::mysql::detail::struct_tag +>::deserialize_( column_definition_packet& output, deserialization_context& ctx ) noexcept diff --git a/include/boost/mysql/detail/protocol/impl/handshake_messages.ipp b/include/boost/mysql/detail/protocol/impl/handshake_messages.ipp index f50e72e1..25ffb215 100644 --- a/include/boost/mysql/detail/protocol/impl/handshake_messages.ipp +++ b/include/boost/mysql/detail/protocol/impl/handshake_messages.ipp @@ -1,9 +1,12 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_HANDSHAKE_MESSAGES_IPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_HANDSHAKE_MESSAGES_IPP_ -#include "boost/mysql/detail/protocol/serialization.hpp" -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::handshake_packet, + boost::mysql::detail::struct_tag +>::deserialize_( handshake_packet& output, deserialization_context& ctx ) noexcept @@ -67,7 +70,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return errc::ok; } -std::size_t boost::mysql::detail::get_size( +std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::detail::handshake_response_packet, + boost::mysql::detail::struct_tag +>::get_size_( const handshake_response_packet& value, const serialization_context& ctx ) noexcept @@ -87,7 +94,11 @@ std::size_t boost::mysql::detail::get_size( return res; } -inline void boost::mysql::detail::serialize( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::detail::handshake_response_packet, + boost::mysql::detail::struct_tag +>::serialize_( const handshake_response_packet& value, serialization_context& ctx ) noexcept @@ -106,7 +117,11 @@ inline void boost::mysql::detail::serialize( serialize(value.client_plugin_name, ctx); } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::auth_switch_request_packet, + boost::mysql::detail::struct_tag +>::deserialize_( auth_switch_request_packet& output, deserialization_context& ctx ) noexcept diff --git a/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp b/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp index 37caa927..71a9becc 100644 --- a/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp +++ b/include/boost/mysql/detail/protocol/impl/prepared_statement_messages.hpp @@ -1,7 +1,6 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_PREPARED_STATEMENT_MESSAGES_HPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_PREPARED_STATEMENT_MESSAGES_HPP_ -#include "boost/mysql/detail/protocol/serialization.hpp" #include "boost/mysql/detail/protocol/null_bitmap_traits.hpp" namespace boost { @@ -88,7 +87,11 @@ inline void serialize_binary_value( } // mysql } // boost -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::com_stmt_prepare_ok_packet, + boost::mysql::detail::struct_tag +>::deserialize_( com_stmt_prepare_ok_packet& output, deserialization_context& ctx ) noexcept @@ -105,7 +108,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( } template -inline std::size_t boost::mysql::detail::get_size( +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::detail::com_stmt_execute_packet, + boost::mysql::detail::struct_tag +>::get_size_( const com_stmt_execute_packet& value, const serialization_context& ctx ) noexcept @@ -127,7 +134,11 @@ inline std::size_t boost::mysql::detail::get_size( } template -inline void boost::mysql::detail::serialize( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::detail::com_stmt_execute_packet, + boost::mysql::detail::struct_tag +>::serialize_( const com_stmt_execute_packet& input, serialization_context& ctx ) noexcept diff --git a/include/boost/mysql/detail/protocol/impl/serialization.hpp b/include/boost/mysql/detail/protocol/impl/serialization.hpp new file mode 100644 index 00000000..736b6813 --- /dev/null +++ b/include/boost/mysql/detail/protocol/impl/serialization.hpp @@ -0,0 +1,310 @@ +#ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_HPP_ +#define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_HPP_ + +namespace boost { +namespace mysql { +namespace detail { + +template +constexpr std::size_t get_int_size() +{ + static_assert(is_fixed_size_int()); + return std::is_same::value ? 3 : + std::is_same::value ? 6 : sizeof(T); +} + +struct is_command_helper +{ + template + static constexpr std::true_type get(decltype(T::command_id)*); + + template + static constexpr std::false_type get(...); +}; + +template +struct is_command : decltype(is_command_helper::get(nullptr)) +{ +}; + +template +errc deserialize_struct( + [[maybe_unused]] T& output, + [[maybe_unused]] deserialization_context& ctx +) noexcept +{ + constexpr auto fields = get_struct_fields::value; + if constexpr (index == std::tuple_size::value) + { + return errc::ok; + } + else + { + constexpr auto pmem = std::get(fields); + errc err = deserialize(output.*pmem, ctx); + if (err != errc::ok) + { + return err; + } + else + { + return deserialize_struct(output, ctx); + } + } +} + +template +void serialize_struct( + [[maybe_unused]] const T& value, + [[maybe_unused]] serialization_context& ctx +) noexcept +{ + constexpr auto fields = get_struct_fields::value; + if constexpr (index < std::tuple_size::value) + { + auto pmem = std::get(fields); + serialize(value.*pmem, ctx); + serialize_struct(value, ctx); + } +} + +template +std::size_t get_size_struct( + [[maybe_unused]] const T& input, + [[maybe_unused]] const serialization_context& ctx +) noexcept +{ + constexpr auto fields = get_struct_fields::value; + if constexpr (index == std::tuple_size::value) + { + return 0; + } + else + { + constexpr auto pmem = std::get(fields); + return get_size_struct(input, ctx) + + get_size(input.*pmem, ctx); + } +} + +} // detail +} // mysql +} // boost + +template +constexpr bool boost::mysql::detail::is_fixed_size_int() +{ + return + std::is_integral>::value && + std::is_base_of>, T>::value && + !std::is_same::value; +} + +template +boost::mysql::errc +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::fixed_size_int_tag +>::deserialize_( + T& output, + deserialization_context& ctx +) noexcept +{ + static_assert(std::is_standard_layout_v); + + constexpr auto size = get_int_size(); + if (!ctx.enough_size(size)) + { + return errc::incomplete_message; + } + + memset(&output.value, 0, sizeof(output.value)); + memcpy(&output.value, ctx.first(), size); + boost::endian::little_to_native_inplace(output); + ctx.advance(size); + + return errc::ok; +} + +template +void boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::fixed_size_int_tag +>::serialize_( + T input, + serialization_context& ctx +) noexcept +{ + boost::endian::native_to_little_inplace(input); + ctx.write(&input.value, get_int_size()); +} + +template +constexpr std::size_t boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::fixed_size_int_tag +>::get_size_(T, const serialization_context&) noexcept +{ + return get_int_size(); +} + +template +boost::mysql::errc boost::mysql::detail::serialization_traits< + boost::mysql::detail::string_fixed, + boost::mysql::detail::no_serialization_tag +>::deserialize_( + string_fixed& output, + deserialization_context& ctx +) noexcept +{ + if (!ctx.enough_size(N)) + { + return errc::incomplete_message; + } + memcpy(&output.value, ctx.first(), N); + ctx.advance(N); + return errc::ok; +} + +template +boost::mysql::errc +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::enum_tag +>::deserialize_( + T& output, + deserialization_context& ctx +) noexcept +{ + serializable_type value; + errc err = deserialize(value, ctx); + if (err != errc::ok) + { + return err; + } + output = static_cast(value.value); + return errc::ok; +} + +template +std::size_t boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::enum_tag +>::get_size_(T, const serialization_context&) noexcept +{ + return get_int_size(); +} + +template +boost::mysql::errc +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::floating_point_tag +>::deserialize_(T& output, deserialization_context& ctx) noexcept +{ + // Size check + if (!ctx.enough_size(sizeof(T))) return errc::incomplete_message; + + // Endianness conversion + // Boost.Endian support for floats start at 1.71. TODO: maybe update requirements and CI +#if BOOST_ENDIAN_BIG_BYTE + char buf [sizeof(T)]; + std::memcpy(buf, ctx.first(), sizeof(T)); + std::reverse(buf, buf + sizeof(T)); + std::memcpy(&output, buf, sizeof(T)); +#else + std::memcpy(&output, ctx.first(), sizeof(T)); +#endif + ctx.advance(sizeof(T)); + return errc::ok; +} + +template +void +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::floating_point_tag +>::serialize_(T input, serialization_context& ctx) noexcept +{ + // Endianness conversion +#if BOOST_ENDIAN_BIG_BYTE + char buf [sizeof(T)]; + std::memcpy(buf, &input, sizeof(T)); + std::reverse(buf, buf + sizeof(T)); + ctx.write(buf, sizeof(T)); +#else + ctx.write(&input, sizeof(T)); +#endif +} + +template +boost::mysql::errc +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::struct_tag +>::deserialize_(T& output, deserialization_context& ctx) noexcept +{ + return deserialize_struct<0>(output, ctx); +} + +template +void +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::struct_tag +>::serialize_( + [[maybe_unused]] const T& input, + [[maybe_unused]] serialization_context& ctx +) noexcept +{ + // For commands, add the command ID. Commands are only sent by the client, + // so this is not considered in the deserialization functions. + if constexpr (is_command::value) + { + serialize(int1(T::command_id), ctx); + } + serialize_struct<0>(input, ctx); +} + +template +std::size_t +boost::mysql::detail::serialization_traits< + T, + boost::mysql::detail::struct_tag +>::get_size_(const T& input, const serialization_context& ctx) noexcept +{ + std::size_t res = is_command::value ? 1 : 0; + res += get_size_struct<0>(input, ctx); + return res; +} + +template +void boost::mysql::detail::serialize_message( + const Serializable& input, + capabilities caps, + basic_bytestring& buffer +) +{ + serialization_context ctx (caps); + std::size_t size = get_size(input, ctx); + buffer.resize(size); + ctx.set_first(buffer.data()); + serialize(input, ctx); + assert(ctx.first() == buffer.data() + buffer.size()); +} + +template +boost::mysql::error_code boost::mysql::detail::deserialize_message( + Deserializable& output, + deserialization_context& ctx +) +{ + auto err = deserialize(output, ctx); + if (err != errc::ok) return make_error_code(err); + if (!ctx.empty()) return make_error_code(errc::extra_bytes); + return error_code(); +} + + + +#endif /* INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_HPP_ */ diff --git a/include/boost/mysql/detail/protocol/impl/serialization.ipp b/include/boost/mysql/detail/protocol/impl/serialization.ipp index 145b1afd..e7405018 100644 --- a/include/boost/mysql/detail/protocol/impl/serialization.ipp +++ b/include/boost/mysql/detail/protocol/impl/serialization.ipp @@ -5,6 +5,14 @@ namespace boost { namespace mysql { namespace detail { +inline std::string_view get_string( + const std::uint8_t* from, + std::size_t size +) +{ + return std::string_view(reinterpret_cast(from), size); +} + inline errc deserialize_binary_date( date& output, std::uint8_t length, @@ -110,7 +118,11 @@ struct broken_time } // mysql } // boost -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::int_lenenc, + boost::mysql::detail::no_serialization_tag +>::deserialize_( int_lenenc& output, deserialization_context& ctx ) noexcept @@ -148,7 +160,12 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return err; } -inline void boost::mysql::detail::serialize( + +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::detail::int_lenenc, + boost::mysql::detail::no_serialization_tag +>::serialize_( int_lenenc input, serialization_context& ctx ) noexcept @@ -174,9 +191,13 @@ inline void boost::mysql::detail::serialize( } } -inline std::size_t boost::mysql::detail::get_size( - int_lenenc input, const - serialization_context& +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::detail::int_lenenc, + boost::mysql::detail::no_serialization_tag +>::get_size_( + int_lenenc input, + const serialization_context& ) noexcept { if (input.value < 251) return 1; @@ -185,7 +206,11 @@ inline std::size_t boost::mysql::detail::get_size( else return 9; } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::string_null, + boost::mysql::detail::no_serialization_tag +>::deserialize_( string_null& output, deserialization_context& ctx ) noexcept @@ -200,7 +225,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return errc::ok; } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::string_eof, + boost::mysql::detail::no_serialization_tag +>::deserialize_( string_eof& output, deserialization_context& ctx ) noexcept @@ -210,7 +239,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return errc::ok; } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::detail::string_lenenc, + boost::mysql::detail::no_serialization_tag +>::deserialize_( string_lenenc& output, deserialization_context& ctx ) noexcept @@ -231,7 +264,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return errc::ok; } -inline std::size_t boost::mysql::detail::get_size( +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::date, + boost::mysql::detail::no_serialization_tag +>::get_size_( const date&, const serialization_context& ) noexcept @@ -240,7 +277,11 @@ inline std::size_t boost::mysql::detail::get_size( return 5; // length, year, month, day } -inline void boost::mysql::detail::serialize( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::date, + boost::mysql::detail::no_serialization_tag +>::serialize_( const date& input, serialization_context& ctx ) noexcept @@ -250,7 +291,11 @@ inline void boost::mysql::detail::serialize( serialize_binary_ymd(::date::year_month_day (input), ctx); } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::date, + boost::mysql::detail::no_serialization_tag +>::deserialize_( date& output, deserialization_context& ctx ) noexcept @@ -262,7 +307,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( } // datetime -inline std::size_t boost::mysql::detail::get_size( +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::datetime, + boost::mysql::detail::no_serialization_tag +>::get_size_( const datetime& input, const serialization_context& ) noexcept @@ -271,7 +320,11 @@ inline std::size_t boost::mysql::detail::get_size( return dt.binary_serialized_length() + 1; // extra length prefix byte } -inline void boost::mysql::detail::serialize( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::datetime, + boost::mysql::detail::no_serialization_tag +>::serialize_( const datetime& input, serialization_context& ctx ) noexcept @@ -299,7 +352,11 @@ inline void boost::mysql::detail::serialize( } } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::datetime, + boost::mysql::detail::no_serialization_tag +>::deserialize_( datetime& output, deserialization_context& ctx ) noexcept @@ -337,7 +394,11 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( } // time -inline std::size_t boost::mysql::detail::get_size( +inline std::size_t +boost::mysql::detail::serialization_traits< + boost::mysql::time, + boost::mysql::detail::no_serialization_tag +>::get_size_( const time& input, const serialization_context& ) noexcept @@ -345,7 +406,11 @@ inline std::size_t boost::mysql::detail::get_size( return broken_time(input).binary_serialized_length() + 1; // length byte } -inline void boost::mysql::detail::serialize( +inline void +boost::mysql::detail::serialization_traits< + boost::mysql::time, + boost::mysql::detail::no_serialization_tag +>::serialize_( const time& input, serialization_context& ctx ) noexcept @@ -372,7 +437,11 @@ inline void boost::mysql::detail::serialize( } } -inline boost::mysql::errc boost::mysql::detail::deserialize( +inline boost::mysql::errc +boost::mysql::detail::serialization_traits< + boost::mysql::time, + boost::mysql::detail::no_serialization_tag +>::deserialize_( time& output, deserialization_context& ctx ) noexcept @@ -417,33 +486,6 @@ inline boost::mysql::errc boost::mysql::detail::deserialize( return errc::ok; } -template -void boost::mysql::detail::serialize_message( - const Serializable& input, - capabilities caps, - basic_bytestring& buffer -) -{ - serialization_context ctx (caps); - std::size_t size = get_size(input, ctx); - buffer.resize(size); - ctx.set_first(buffer.data()); - serialize(input, ctx); - assert(ctx.first() == buffer.data() + buffer.size()); -} - -template -boost::mysql::error_code boost::mysql::detail::deserialize_message( - Deserializable& output, - deserialization_context& ctx -) -{ - auto err = deserialize(output, ctx); - if (err != errc::ok) return make_error_code(err); - if (!ctx.empty()) return make_error_code(errc::extra_bytes); - return error_code(); -} - inline std::pair boost::mysql::detail::deserialize_message_type( deserialization_context& ctx @@ -464,6 +506,4 @@ boost::mysql::detail::deserialize_message_type( } - - #endif /* INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_SERIALIZATION_IPP_ */ diff --git a/include/boost/mysql/detail/protocol/prepared_statement_messages.hpp b/include/boost/mysql/detail/protocol/prepared_statement_messages.hpp index d1824e4b..097db372 100644 --- a/include/boost/mysql/detail/protocol/prepared_statement_messages.hpp +++ b/include/boost/mysql/detail/protocol/prepared_statement_messages.hpp @@ -1,13 +1,9 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_PREPARED_STATEMENT_MESSAGES_HPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_PREPARED_STATEMENT_MESSAGES_HPP_ -#include "boost/mysql/detail/protocol/protocol_types.hpp" -#include "boost/mysql/detail/protocol/serialization_context.hpp" -#include "boost/mysql/detail/protocol/deserialization_context.hpp" +#include "boost/mysql/detail/protocol/serialization.hpp" #include "boost/mysql/detail/protocol/constants.hpp" -#include "boost/mysql/detail/auxiliar/get_struct_fields.hpp" #include "boost/mysql/value.hpp" -#include "boost/mysql/error.hpp" namespace boost { namespace mysql { @@ -107,13 +103,20 @@ struct get_struct_fields ); }; -inline errc deserialize(com_stmt_prepare_ok_packet& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits : noop_serialization_traits +{ + static inline errc deserialize_(com_stmt_prepare_ok_packet& output, deserialization_context& ctx) noexcept; +}; -template -inline std::size_t get_size(const com_stmt_execute_packet& value, const serialization_context& ctx) noexcept; - -template -inline void serialize(const com_stmt_execute_packet& input, serialization_context& ctx) noexcept; +template +struct serialization_traits, struct_tag>: noop_serialization_traits +{ + static inline std::size_t get_size_(const com_stmt_execute_packet& value, + const serialization_context& ctx) noexcept; + static inline void serialize_(const com_stmt_execute_packet& input, + serialization_context& ctx) noexcept; +}; } // detail } // mysql diff --git a/include/boost/mysql/detail/protocol/query_messages.hpp b/include/boost/mysql/detail/protocol/query_messages.hpp index 99e160e4..74c7043f 100644 --- a/include/boost/mysql/detail/protocol/query_messages.hpp +++ b/include/boost/mysql/detail/protocol/query_messages.hpp @@ -1,8 +1,7 @@ #ifndef INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_QUERY_MESSAGES_HPP_ #define INCLUDE_BOOST_MYSQL_DETAIL_PROTOCOL_QUERY_MESSAGES_HPP_ -#include "boost/mysql/detail/protocol/protocol_types.hpp" -#include "boost/mysql/detail/auxiliar/get_struct_fields.hpp" +#include "boost/mysql/detail/protocol/serialization.hpp" #include namespace boost { diff --git a/include/boost/mysql/detail/protocol/serialization.hpp b/include/boost/mysql/detail/protocol/serialization.hpp index dcbb8df6..97eb5a61 100644 --- a/include/boost/mysql/detail/protocol/serialization.hpp +++ b/include/boost/mysql/detail/protocol/serialization.hpp @@ -16,346 +16,200 @@ namespace boost { namespace mysql { namespace detail { +struct no_serialization_tag {}; -/** - * Base forms: - * errc deserialize(T& output, deserialization_context&) noexcept - * void serialize(const T& input, SerializationContext&) noexcept - * std::size_t get_size(const T& input, const SerializationContext&) noexcept - */ +template +struct get_serialization_tag; + +template ::type> +struct serialization_traits; + +template +errc deserialize(T& output, deserialization_context& ctx) noexcept +{ + return serialization_traits::deserialize_(output, ctx); +} + +template +void serialize(const T& input, serialization_context& ctx) noexcept +{ + serialization_traits::serialize_(input, ctx); +} + +template +std::size_t get_size(const T& input, const serialization_context& ctx) noexcept +{ + return serialization_traits::get_size_(input, ctx); +} // Fixed-size integers +struct fixed_size_int_tag {}; + template -struct is_fixed_size_int +constexpr bool is_fixed_size_int(); + +template +struct serialization_traits { - static constexpr bool value = - std::is_integral>::value && - std::is_base_of>, T>::value; + static errc deserialize_(T& output, deserialization_context& ctx) noexcept; + static void serialize_(T input, serialization_context& ctx) noexcept; + static constexpr std::size_t get_size_(T, const serialization_context&) noexcept; }; -template <> struct is_fixed_size_int : std::false_type {}; - -template constexpr bool is_fixed_size_v = is_fixed_size_int::value; - -template -struct get_int_size -{ - static_assert(is_fixed_size_v); - static constexpr std::size_t value = sizeof(T::value); -}; - -template <> struct get_int_size { static constexpr std::size_t value = 3; }; -template <> struct get_int_size { static constexpr std::size_t value = 6; }; - -template -std::enable_if_t::value, errc> -deserialize(T& output, deserialization_context& ctx) noexcept -{ - static_assert(std::is_standard_layout_v); - - constexpr auto size = get_int_size::value; - if (!ctx.enough_size(size)) - { - return errc::incomplete_message; - } - - memset(&output.value, 0, sizeof(output.value)); - memcpy(&output.value, ctx.first(), size); - boost::endian::little_to_native_inplace(output); - ctx.advance(size); - - return errc::ok; -} - -template -std::enable_if_t> -serialize(T input, serialization_context& ctx) noexcept -{ - boost::endian::native_to_little_inplace(input); - ctx.write(&input.value, get_int_size::value); -} - -template -constexpr std::enable_if_t, std::size_t> -get_size(T, const serialization_context&) noexcept -{ - return get_int_size::value; -} - // int_lenenc -inline errc deserialize(int_lenenc& output, deserialization_context& ctx) noexcept; -inline void serialize(int_lenenc input, serialization_context& ctx) noexcept; -inline std::size_t get_size(int_lenenc input, const serialization_context&) noexcept; - -// Helper for strings -inline std::string_view get_string(const std::uint8_t* from, std::size_t size) +template <> +struct serialization_traits { - return std::string_view (reinterpret_cast(from), size); -} + static inline errc deserialize_(int_lenenc& output, deserialization_context& ctx) noexcept; + static inline void serialize_(int_lenenc input, serialization_context& ctx) noexcept; + static inline std::size_t get_size_(int_lenenc input, const serialization_context&) noexcept; +}; + // string_fixed template -errc deserialize(string_fixed& output, deserialization_context& ctx) noexcept +struct serialization_traits, no_serialization_tag> { - if (!ctx.enough_size(N)) + static errc deserialize_(string_fixed& output, deserialization_context& ctx) noexcept; + static void serialize_(const string_fixed& input, serialization_context& ctx) noexcept { - return errc::incomplete_message; + ctx.write(input.value.data(), N); } - memcpy(&output.value, ctx.first(), N); - ctx.advance(N); - return errc::ok; -} + static constexpr std::size_t get_size_(const string_fixed&, const serialization_context&) noexcept + { + return N; + } +}; -template -void serialize(const string_fixed& input, serialization_context& ctx) noexcept -{ - ctx.write(input.value.data(), N); -} - -template -constexpr std::size_t get_size(const string_fixed&, const serialization_context&) noexcept -{ - return N; -} // string_null -inline errc deserialize(string_null& output, deserialization_context& ctx) noexcept; -inline void serialize(string_null input, serialization_context& ctx) noexcept +template <> +struct serialization_traits { - ctx.write(input.value.data(), input.value.size()); - ctx.write(0); // null terminator -} -inline std::size_t get_size(string_null input, const serialization_context&) noexcept -{ - return input.value.size() + 1; -} + static inline errc deserialize_(string_null& output, deserialization_context& ctx) noexcept; + static inline void serialize_(string_null input, serialization_context& ctx) noexcept + { + ctx.write(input.value.data(), input.value.size()); + ctx.write(0); // null terminator + } + static inline std::size_t get_size_(string_null input, const serialization_context&) noexcept + { + return input.value.size() + 1; + } +}; // string_eof -inline errc deserialize(string_eof& output, deserialization_context& ctx) noexcept; -inline void serialize(string_eof input, serialization_context& ctx) noexcept +template <> +struct serialization_traits { - ctx.write(input.value.data(), input.value.size()); -} -inline std::size_t get_size(string_eof input, const serialization_context&) noexcept -{ - return input.value.size(); -} + static inline errc deserialize_(string_eof& output, deserialization_context& ctx) noexcept; + static inline void serialize_(string_eof input, serialization_context& ctx) noexcept + { + ctx.write(input.value.data(), input.value.size()); + } + static inline std::size_t get_size_(string_eof input, const serialization_context&) noexcept + { + return input.value.size(); + } +}; // string_lenenc -inline errc deserialize(string_lenenc& output, deserialization_context& ctx) noexcept; -inline void serialize(string_lenenc input, serialization_context& ctx) noexcept +template <> +struct serialization_traits { - serialize(int_lenenc(input.value.size()), ctx); - ctx.write(input.value.data(), input.value.size()); -} -inline std::size_t get_size(string_lenenc input, const serialization_context& ctx) noexcept -{ - return get_size(int_lenenc(input.value.size()), ctx) + input.value.size(); -} + static inline errc deserialize_(string_lenenc& output, deserialization_context& ctx) noexcept; + static inline void serialize_(string_lenenc input, serialization_context& ctx) noexcept + { + serialize(int_lenenc(input.value.size()), ctx); + ctx.write(input.value.data(), input.value.size()); + } + static inline std::size_t get_size_(string_lenenc input, const serialization_context& ctx) noexcept + { + return get_size(int_lenenc(input.value.size()), ctx) + input.value.size(); + } +}; // Enums -template >> -errc deserialize(T& output, deserialization_context& ctx) noexcept +struct enum_tag {}; + +template +struct serialization_traits { - value_holder> value; - errc err = deserialize(value, ctx); - if (err != errc::ok) + using underlying_type = std::underlying_type_t; + using serializable_type = value_holder; + + static errc deserialize_(T& output, deserialization_context& ctx) noexcept; + static void serialize_(T input, serialization_context& ctx) noexcept { - return err; + serialize(serializable_type(static_cast(input)), ctx); } - output = static_cast(value.value); - return errc::ok; -} - -template >> -void serialize(T input, serialization_context& ctx) noexcept -{ - value_holder> value {static_cast>(input)}; - serialize(value, ctx); -} - -template >> -std::size_t get_size(T, const serialization_context&) noexcept -{ - return get_int_size>>::value; -} + static std::size_t get_size_(T, const serialization_context&) noexcept; +}; // Floating points -static_assert(std::numeric_limits::is_iec559); -static_assert(std::numeric_limits::is_iec559); +struct floating_point_tag {}; template -std::enable_if_t, errc> -deserialize(T& output, deserialization_context& ctx) noexcept +struct serialization_traits { - // Size check - if (!ctx.enough_size(sizeof(T))) return errc::incomplete_message; + static_assert(std::numeric_limits::is_iec559); + static errc deserialize_(T& output, deserialization_context& ctx) noexcept; + static void serialize_(T input, serialization_context& ctx) noexcept; + static std::size_t get_size_(T, const serialization_context&) noexcept + { + return sizeof(T); + } +}; - // Endianness conversion - // Boost.Endian support for floats start at 1.71. TODO: maybe update requirements and CI -#if BOOST_ENDIAN_BIG_BYTE - char buf [sizeof(T)]; - std::memcpy(buf, ctx.first(), sizeof(T)); - std::reverse(buf, buf + sizeof(T)); - std::memcpy(&output, buf, sizeof(T)); -#else - std::memcpy(&output, ctx.first(), sizeof(T)); -#endif - ctx.advance(sizeof(T)); - return errc::ok; -} - -template -std::enable_if_t> -serialize(T input, serialization_context& ctx) noexcept -{ - // Endianness conversion -#if BOOST_ENDIAN_BIG_BYTE - char buf [sizeof(T)]; - std::memcpy(buf, &input, sizeof(T)); - std::reverse(buf, buf + sizeof(T)); - ctx.write(buf, sizeof(T)); -#else - ctx.write(&input, sizeof(T)); -#endif -} - -template -std::enable_if_t, std::size_t> -get_size(T, const serialization_context&) noexcept -{ - return sizeof(T); -} // Dates and times -inline std::size_t get_size(const date& input, const serialization_context& ctx) noexcept; -inline void serialize(const date& input, serialization_context& ctx) noexcept; -inline errc deserialize(date& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits +{ + static inline std::size_t get_size_(const date& input, const serialization_context& ctx) noexcept; + static inline void serialize_(const date& input, serialization_context& ctx) noexcept; + static inline errc deserialize_(date& output, deserialization_context& ctx) noexcept; +}; -inline std::size_t get_size(const datetime& input, const serialization_context& ctx) noexcept; -inline void serialize(const datetime& input, serialization_context& ctx) noexcept; -inline errc deserialize(datetime& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits +{ + static inline std::size_t get_size_(const datetime& input, const serialization_context& ctx) noexcept; + static inline void serialize_(const datetime& input, serialization_context& ctx) noexcept; + static inline errc deserialize_(datetime& output, deserialization_context& ctx) noexcept; +}; -inline std::size_t get_size(const time& input, const serialization_context& ctx) noexcept; -inline void serialize(const time& input, serialization_context& ctx) noexcept; -inline errc deserialize(time& output, deserialization_context& ctx) noexcept; +template <> +struct serialization_traits +{ + static inline std::size_t get_size_(const time& input, const serialization_context& ctx) noexcept; + static inline void serialize_(const time& input, serialization_context& ctx) noexcept; + static inline errc deserialize_(time& output, deserialization_context& ctx) noexcept; +}; // Structs and commands (messages) +struct struct_tag {}; + template -struct is_struct_with_fields +constexpr bool is_struct_with_fields() { - static constexpr bool value = !std::is_same_v< + return !std::is_same_v< std::decay_t::value)>, not_a_struct_with_fields >; +} + + + + +template +struct serialization_traits +{ + static errc deserialize_(T& output, deserialization_context& ctx) noexcept; + static void serialize_(const T& input, serialization_context& ctx) noexcept; + static std::size_t get_size_(const T& input, const serialization_context& ctx) noexcept; }; -struct is_command_helper -{ - template - static constexpr std::true_type get(decltype(T::command_id)*); - - template - static constexpr std::false_type get(...); -}; - -template -struct is_command : decltype(is_command_helper::get(nullptr)) -{ -}; - -template -errc deserialize_struct( - [[maybe_unused]] T& output, - [[maybe_unused]] deserialization_context& ctx -) noexcept -{ - constexpr auto fields = get_struct_fields::value; - if constexpr (index == std::tuple_size::value) - { - return errc::ok; - } - else - { - constexpr auto pmem = std::get(fields); - errc err = deserialize(output.*pmem, ctx); - if (err != errc::ok) - { - return err; - } - else - { - return deserialize_struct(output, ctx); - } - } -} - -template -std::enable_if_t::value, errc> -deserialize(T& output, deserialization_context& ctx) noexcept -{ - return deserialize_struct<0>(output, ctx); -} - -template -void serialize_struct( - [[maybe_unused]] const T& value, - [[maybe_unused]] serialization_context& ctx -) noexcept -{ - constexpr auto fields = get_struct_fields::value; - if constexpr (index < std::tuple_size::value) - { - auto pmem = std::get(fields); - serialize(value.*pmem, ctx); - serialize_struct(value, ctx); - } -} - -template -std::enable_if_t::value> -serialize( - [[maybe_unused]] const T& input, - [[maybe_unused]] serialization_context& ctx -) noexcept -{ - // For commands, add the command ID. Commands are only sent by the client, - // so this is not considered in the deserialization functions. - if constexpr (is_command::value) - { - serialize(int1(T::command_id), ctx); - } - serialize_struct<0>(input, ctx); -} - -template -std::size_t get_size_struct( - [[maybe_unused]] const T& input, - [[maybe_unused]] const serialization_context& ctx -) noexcept -{ - constexpr auto fields = get_struct_fields::value; - if constexpr (index == std::tuple_size::value) - { - return 0; - } - else - { - constexpr auto pmem = std::get(fields); - return get_size_struct(input, ctx) + - get_size(input.*pmem, ctx); - } -} - -template -std::enable_if_t::value, std::size_t> -get_size(const T& input, const serialization_context& ctx) noexcept -{ - std::size_t res = is_command::value ? 1 : 0; - res += get_size_struct<0>(input, ctx); - return res; -} - // Helper to serialize top-level messages template void serialize_message( @@ -370,17 +224,30 @@ error_code deserialize_message( deserialization_context& ctx ); - - - // Dummy type to indicate no (de)serialization is required struct dummy_serializable { explicit dummy_serializable(...) {} // Make it constructible from anything }; -inline std::size_t get_size(dummy_serializable, const serialization_context&) noexcept { return 0; } -inline void serialize(dummy_serializable, serialization_context&) noexcept {} -inline errc deserialize(dummy_serializable, deserialization_context&) noexcept { return errc::ok; } + +/*struct noop_serialization_traits +{ + static inline std::size_t get_size_(dummy_serializable, const serialization_context&) noexcept { return 0; } + static inline void serialize_(dummy_serializable, serialization_context&) noexcept {} + static inline errc deserialize_(dummy_serializable, deserialization_context&) noexcept { return errc::ok; } +};*/ + +struct noop_serialization_traits +{ + static inline std::size_t get_size_(...) noexcept { return 0; } + static inline void serialize_(...) noexcept {} + static inline errc deserialize_(...) noexcept { return errc::ok; } +}; + +template <> +struct serialization_traits : noop_serialization_traits +{ +}; // Helpers for (de) serializing a set of fields @@ -418,10 +285,30 @@ inline std::pair deserialize_message_type( deserialization_context& ctx ); +template +struct get_serialization_tag : std::conditional< + is_fixed_size_int(), + fixed_size_int_tag, + std::conditional_t< + std::is_floating_point::value, + floating_point_tag, + std::conditional_t< + std::is_enum::value, + enum_tag, + std::conditional_t< + is_struct_with_fields(), + struct_tag, + no_serialization_tag + > + > + > +> {}; + } // detail } // mysql } // boost +#include "boost/mysql/detail/protocol/impl/serialization.hpp" #include "boost/mysql/detail/protocol/impl/serialization.ipp" #endif diff --git a/test/common/serialization_test_common.hpp b/test/common/serialization_test_common.hpp index 1f252477..2da4a68d 100644 --- a/test/common/serialization_test_common.hpp +++ b/test/common/serialization_test_common.hpp @@ -2,10 +2,6 @@ #define TEST_SERIALIZATION_TEST_COMMON_HPP_ #include "boost/mysql/detail/protocol/serialization.hpp" -#include "boost/mysql/detail/protocol/common_messages.hpp" -#include "boost/mysql/detail/protocol/handshake_messages.hpp" -#include "boost/mysql/detail/protocol/query_messages.hpp" -#include "boost/mysql/detail/protocol/prepared_statement_messages.hpp" #include "boost/mysql/detail/protocol/constants.hpp" #include "boost/mysql/value.hpp" #include @@ -49,7 +45,7 @@ bool equals_struct(const T& lhs, const T& rhs) } template -std::enable_if_t::value, bool> +std::enable_if_t(), bool> operator==(const T& lhs, const T& rhs) { return equals_struct<0>(lhs, rhs); @@ -84,7 +80,7 @@ void print_struct(std::ostream& os, const T& value) } template -std::enable_if_t::value, std::ostream&> +std::enable_if_t(), std::ostream&> operator<<(std::ostream& os, const T& value) { os << boost::typeindex::type_id().pretty_name() << "(\n"; diff --git a/test/unit/detail/protocol/serialization.cpp b/test/unit/detail/protocol/serialization.cpp index b516ea61..72caa339 100644 --- a/test/unit/detail/protocol/serialization.cpp +++ b/test/unit/detail/protocol/serialization.cpp @@ -6,6 +6,10 @@ */ #include "serialization_test_common.hpp" +#include "boost/mysql/detail/protocol/common_messages.hpp" +#include "boost/mysql/detail/protocol/handshake_messages.hpp" +#include "boost/mysql/detail/protocol/query_messages.hpp" +#include "boost/mysql/detail/protocol/prepared_statement_messages.hpp" #include "test_common.hpp" #include #include