diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cd49fc..717dc5b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,9 @@ set(source_files src/binsrv/event/common_header.hpp src/binsrv/event/common_header.cpp + src/binsrv/event/common_header_flag_type_fwd.hpp + src/binsrv/event/common_header_flag_type.hpp + src/binsrv/event/empty_body_fwd.hpp src/binsrv/event/empty_body.hpp src/binsrv/event/empty_body.cpp @@ -108,9 +111,6 @@ set(source_files src/binsrv/event/event.hpp src/binsrv/event/event.cpp - src/binsrv/event/flag_type_fwd.hpp - src/binsrv/event/flag_type.hpp - src/binsrv/event/footer_fwd.hpp src/binsrv/event/footer.hpp src/binsrv/event/footer.cpp @@ -129,6 +129,17 @@ set(source_files src/binsrv/event/generic_post_header_fwd.hpp src/binsrv/event/generic_post_header.hpp + src/binsrv/event/gtid_log_flag_type_fwd.hpp + src/binsrv/event/gtid_log_flag_type.hpp + + src/binsrv/event/gtid_log_body_impl_fwd.hpp + src/binsrv/event/gtid_log_body_impl.hpp + src/binsrv/event/gtid_log_body_impl.cpp + + src/binsrv/event/gtid_log_post_header_impl_fwd.hpp + src/binsrv/event/gtid_log_post_header_impl.hpp + src/binsrv/event/gtid_log_post_header_impl.cpp + src/binsrv/event/protocol_traits_fwd.hpp src/binsrv/event/protocol_traits.hpp src/binsrv/event/protocol_traits.cpp @@ -159,6 +170,9 @@ set(source_files src/binsrv/event/unknown_post_header.hpp src/binsrv/event/unknown_post_header.cpp + # gtid data structure files + src/binsrv/gtid/common_types.hpp + # binlog files src/binsrv/basic_logger_fwd.hpp src/binsrv/basic_logger.hpp @@ -299,9 +313,8 @@ set(source_files add_executable(binlog_server ${source_files}) target_link_libraries(binlog_server - PUBLIC - binlog_server_compiler_flags PRIVATE + binlog_server_compiler_flags Boost::headers Boost::url ZLIB::ZLIB MySQL::client diff --git a/src/app.cpp b/src/app.cpp index 9be558d..9dfa9cf 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -51,8 +51,8 @@ #include "binsrv/time_unit.hpp" #include "binsrv/event/code_type.hpp" +#include "binsrv/event/common_header_flag_type.hpp" #include "binsrv/event/event.hpp" -#include "binsrv/event/flag_type.hpp" #include "binsrv/event/protocol_traits_fwd.hpp" #include "binsrv/event/reader_context.hpp" @@ -325,7 +325,7 @@ void process_binlog_event(const binsrv::event::event ¤t_event, const auto code = current_common_header.get_type_code(); const auto is_artificial{current_common_header.get_flags().has_element( - binsrv::event::flag_type::artificial)}; + binsrv::event::common_header_flag_type::artificial)}; const auto is_pseudo{current_common_header.get_next_event_position_raw() == 0U}; diff --git a/src/binsrv/event/common_header.cpp b/src/binsrv/event/common_header.cpp index d631961..ee88c88 100644 --- a/src/binsrv/event/common_header.cpp +++ b/src/binsrv/event/common_header.cpp @@ -26,7 +26,7 @@ #include #include "binsrv/event/code_type.hpp" -#include "binsrv/event/flag_type.hpp" +#include "binsrv/event/common_header_flag_type.hpp" #include "util/byte_span_extractors.hpp" #include "util/byte_span_fwd.hpp" @@ -106,8 +106,8 @@ common_header::get_readable_type_code() const noexcept { return to_string_view(get_type_code()); } -[[nodiscard]] flag_set common_header::get_flags() const noexcept { - return flag_set{get_flags_raw()}; +[[nodiscard]] common_header_flag_set common_header::get_flags() const noexcept { + return common_header_flag_set{get_flags_raw()}; } [[nodiscard]] std::string common_header::get_readable_flags() const { diff --git a/src/binsrv/event/common_header.hpp b/src/binsrv/event/common_header.hpp index 5c990cf..c878930 100644 --- a/src/binsrv/event/common_header.hpp +++ b/src/binsrv/event/common_header.hpp @@ -24,7 +24,7 @@ #include #include "binsrv/event/code_type_fwd.hpp" -#include "binsrv/event/flag_type_fwd.hpp" +#include "binsrv/event/common_header_flag_type_fwd.hpp" #include "binsrv/event/protocol_traits_fwd.hpp" #include "util/byte_span_fwd.hpp" @@ -66,7 +66,7 @@ class [[nodiscard]] common_header { } [[nodiscard]] std::uint16_t get_flags_raw() const noexcept { return flags_; } - [[nodiscard]] flag_set get_flags() const noexcept; + [[nodiscard]] common_header_flag_set get_flags() const noexcept; [[nodiscard]] std::string get_readable_flags() const; private: diff --git a/src/binsrv/event/flag_type.hpp b/src/binsrv/event/common_header_flag_type.hpp similarity index 56% rename from src/binsrv/event/flag_type.hpp rename to src/binsrv/event/common_header_flag_type.hpp index 8b66674..4807bd3 100644 --- a/src/binsrv/event/flag_type.hpp +++ b/src/binsrv/event/common_header_flag_type.hpp @@ -13,10 +13,10 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#ifndef BINSRV_EVENT_FLAG_TYPE_HPP -#define BINSRV_EVENT_FLAG_TYPE_HPP +#ifndef BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_HPP +#define BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_HPP -#include "binsrv/event/flag_type_fwd.hpp" // IWYU pragma: export +#include "binsrv/event/common_header_flag_type_fwd.hpp" // IWYU pragma: export #include #include @@ -40,40 +40,41 @@ namespace binsrv::event { // closed). // Events received via network stream should never have this flag set. // clang-format off -#define BINSRV_EVENT_FLAG_TYPE_XY_SEQUENCE() \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(binlog_in_use , 0x001U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(thread_specific, 0x004U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(suppress_use , 0x008U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(artificial , 0x020U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(relay_log , 0x040U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(ignorable , 0x080U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(no_filter , 0x100U), \ - BINSRV_EVENT_FLAG_TYPE_XY_MACRO(mts_isolate , 0x200U) +#define BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_SEQUENCE() \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(binlog_in_use , 0x001U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(thread_specific, 0x004U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(suppress_use , 0x008U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(artificial , 0x020U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(relay_log , 0x040U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(ignorable , 0x080U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(no_filter , 0x100U), \ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(mts_isolate , 0x200U) // clang-format on -#define BINSRV_EVENT_FLAG_TYPE_XY_MACRO(X, Y) X = Y +#define BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(X, Y) X = Y // NOLINTNEXTLINE(readability-enum-initial-value,cert-int09-c) -enum class flag_type : std::uint16_t { - BINSRV_EVENT_FLAG_TYPE_XY_SEQUENCE(), +enum class common_header_flag_type : std::uint16_t { + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_SEQUENCE(), delimiter }; -#undef BINSRV_EVENT_FLAG_TYPE_XY_MACRO +#undef BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO -inline std::string_view to_string_view(flag_type code) noexcept { +inline std::string_view to_string_view(common_header_flag_type code) noexcept { using namespace std::string_view_literals; - using nv_pair = std::pair; -#define BINSRV_EVENT_FLAG_TYPE_XY_MACRO(X, Y) \ - nv_pair { flag_type::X, #X##sv } - static constexpr std::array labels{BINSRV_EVENT_FLAG_TYPE_XY_SEQUENCE(), - nv_pair{flag_type::delimiter, ""sv}}; -#undef BINSRV_EVENT_FLAG_TYPE_XY_MACRO + using nv_pair = std::pair; +#define BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO(X, Y) \ + nv_pair { common_header_flag_type::X, #X##sv } + static constexpr std::array labels{ + BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_SEQUENCE(), + nv_pair{common_header_flag_type::delimiter, ""sv}}; +#undef BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_MACRO // NOLINTNEXTLINE(llvm-qualified-auto,readability-qualified-auto) const auto fnd{std::ranges::find(labels, code, &nv_pair::first)}; return fnd == std::end(labels) ? ""sv : fnd->second; } -#undef BINSRV_EVENT_FLAG_TYPE_XY_SEQUENCE +#undef BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_XY_SEQUENCE // NOLINTEND(cppcoreguidelines-macro-usage) } // namespace binsrv::event -#endif // BINSRV_EVENT_FLAG_TYPE_HPP +#endif // BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_HPP diff --git a/src/binsrv/event/common_header_flag_type_fwd.hpp b/src/binsrv/event/common_header_flag_type_fwd.hpp new file mode 100644 index 0000000..4f3d1a5 --- /dev/null +++ b/src/binsrv/event/common_header_flag_type_fwd.hpp @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_FWD_HPP +#define BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_FWD_HPP + +#include + +#include "util/flag_set_fwd.hpp" + +namespace binsrv::event { + +// NOLINTNEXTLINE(readability-enum-initial-value,cert-int09-c) +enum class common_header_flag_type : std::uint16_t; + +using common_header_flag_set = util::flag_set; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_COMMON_HEADER_FLAG_TYPE_FWD_HPP diff --git a/src/binsrv/event/event.hpp b/src/binsrv/event/event.hpp index bd89f72..07bfde9 100644 --- a/src/binsrv/event/event.hpp +++ b/src/binsrv/event/event.hpp @@ -36,8 +36,10 @@ #include "binsrv/event/footer.hpp" // IWYU pragma: export #include "binsrv/event/format_description_body_impl.hpp" // IWYU pragma: export #include "binsrv/event/format_description_post_header_impl.hpp" // IWYU pragma: export -#include "binsrv/event/generic_body.hpp" // IWYU pragma: export -#include "binsrv/event/generic_post_header.hpp" // IWYU pragma: export +#include "binsrv/event/generic_body.hpp" // IWYU pragma: export +#include "binsrv/event/generic_post_header.hpp" // IWYU pragma: export +#include "binsrv/event/gtid_log_body_impl.hpp" // IWYU pragma: export +#include "binsrv/event/gtid_log_post_header_impl.hpp" // IWYU pragma: export #include "binsrv/event/reader_context_fwd.hpp" #include "binsrv/event/rotate_body_impl.hpp" // IWYU pragma: export #include "binsrv/event/rotate_post_header_impl.hpp" // IWYU pragma: export diff --git a/src/binsrv/event/gtid_log_body_impl.cpp b/src/binsrv/event/gtid_log_body_impl.cpp new file mode 100644 index 0000000..792a0f0 --- /dev/null +++ b/src/binsrv/event/gtid_log_body_impl.cpp @@ -0,0 +1,170 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/event/gtid_log_body_impl.hpp" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "binsrv/event/code_type.hpp" + +#include "util/byte_span.hpp" +#include "util/byte_span_extractors.hpp" +#include "util/exception_location_helpers.hpp" +#include "util/semantic_version.hpp" + +namespace binsrv::event { + +generic_body_impl::generic_body_impl( + util::const_byte_span portion) { + // TODO: rework with direct member initialization + + // make sure we did OK with data members reordering + static_assert(sizeof *this == boost::alignment::align_up( + sizeof immediate_commit_timestamp_ + + sizeof original_commit_timestamp_ + + sizeof transaction_length_ + + sizeof original_server_version_ + + sizeof immediate_server_version_ + + sizeof commit_group_ticket_, + alignof(decltype(*this))), + "inefficient data member reordering in gtid_log event body"); + + // https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/src/control_events.cpp#L513 + // https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.cpp#L558 + auto remainder = portion; + + if (!util::extract_fixed_int_from_byte_span_checked( + remainder, immediate_commit_timestamp_, + commit_timestamp_field_length)) { + util::exception_location().raise( + "gtid_log event body is too short to extract immediate commit " + "timestamp"); + } + const std::uint64_t commit_timestamp_mask{ + 1ULL << (commit_timestamp_field_length * 8ULL - 1ULL)}; + if ((immediate_commit_timestamp_ & commit_timestamp_mask) != 0ULL) { + // clearing the most significant bit in the + // commit_timestamp_field_length-byte sequence + original_commit_timestamp_ &= ~commit_timestamp_mask; + if (!util::extract_fixed_int_from_byte_span_checked( + remainder, original_commit_timestamp_, + commit_timestamp_field_length)) { + util::exception_location().raise( + "gtid_log event body is too short to extract original commit " + "timestamp"); + } + } + + if (!util::extract_packed_int_from_byte_span_checked(remainder, + transaction_length_)) { + util::exception_location().raise( + "unable to extract transaction length from the gtid_log event body"); + } + + if (std::size(remainder) >= server_version_field_length) { + util::extract_fixed_int_from_byte_span(remainder, original_server_version_); + const std::uint32_t server_version_mask{ + 1ULL << (server_version_field_length * 8ULL - 1ULL)}; + if ((original_server_version_ & server_version_mask) != 0UL) { + original_server_version_ &= ~server_version_mask; + if (!util::extract_fixed_int_from_byte_span_checked( + remainder, immediate_server_version_)) { + util::exception_location().raise( + "gtid_log event body is too short to extract immediate server " + "version"); + } + } + if (std::size(remainder) >= commit_group_ticket_field_length) { + util::extract_fixed_int_from_byte_span(remainder, commit_group_ticket_); + } + } + if (std::size(remainder) != 0U) { + util::exception_location().raise( + "extra bytes in the gtid_log event body"); + } +} +[[nodiscard]] std::string generic_body_impl< + code_type::gtid_log>::get_readable_immediate_commit_timestamp() const { + // threre is still no way to get string representationof the + // std::chrono::high_resolution_clock::time_point using standard stdlib means, + // so using boost::posix_time::ptime here + boost::posix_time::ptime timestamp{ + boost::posix_time::from_time_t(std::time_t{})}; + timestamp += + boost::posix_time::microseconds{get_immediate_commit_timestamp_raw()}; + return boost::posix_time::to_iso_extended_string(timestamp); +} + +[[nodiscard]] util::semantic_version +generic_body_impl::get_original_server_version() + const noexcept { + return util::semantic_version{get_original_server_version_raw()}; +} + +[[nodiscard]] std::string +generic_body_impl::get_readable_original_server_version() + const { + return get_original_server_version().get_string(); +} + +[[nodiscard]] util::semantic_version +generic_body_impl::get_immediate_server_version() + const noexcept { + return util::semantic_version{get_immediate_server_version_raw()}; +} + +[[nodiscard]] std::string +generic_body_impl::get_readable_immediate_server_version() + const { + return get_immediate_server_version().get_string(); +} + +std::ostream &operator<<(std::ostream &output, + const generic_body_impl &obj) { + output << "immediate_commit_timestamp: " + << obj.get_readable_immediate_commit_timestamp(); + if (obj.has_original_commit_timestamp()) { + output << ", original_commit_timestamp: " + << obj.get_original_commit_timestamp_raw(); + } + if (obj.has_transaction_length()) { + output << ", transaction_length: " << obj.get_transaction_length_raw(); + } + if (obj.has_original_server_version()) { + output << ", original_server_version: " + << obj.get_readable_original_server_version(); + } + if (obj.has_immediate_server_version()) { + output << ", immediate_server_version: " + << obj.get_readable_immediate_server_version(); + } + if (obj.has_commit_group_ticket()) { + output << ", commit_group_ticket: " << obj.get_commit_group_ticket_raw(); + } + + return output; +} + +} // namespace binsrv::event diff --git a/src/binsrv/event/gtid_log_body_impl.hpp b/src/binsrv/event/gtid_log_body_impl.hpp new file mode 100644 index 0000000..9e4e9bb --- /dev/null +++ b/src/binsrv/event/gtid_log_body_impl.hpp @@ -0,0 +1,114 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_GTID_LOG_BODY_IMPL_HPP +#define BINSRV_EVENT_GTID_LOG_BODY_IMPL_HPP + +#include "binsrv/event/gtid_log_body_impl_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include +#include + +#include "util/byte_span_fwd.hpp" +#include "util/common_optional_types.hpp" +#include "util/semantic_version_fwd.hpp" + +namespace binsrv::event { + +template <> class [[nodiscard]] generic_body_impl { +public: + explicit generic_body_impl(util::const_byte_span portion); + + [[nodiscard]] std::uint64_t + get_immediate_commit_timestamp_raw() const noexcept { + return immediate_commit_timestamp_; + } + [[nodiscard]] std::chrono::high_resolution_clock::time_point + get_immediate_commit_timestamp() const noexcept { + return std::chrono::high_resolution_clock::time_point{ + std::chrono::microseconds(get_immediate_commit_timestamp_raw())}; + } + [[nodiscard]] std::string get_readable_immediate_commit_timestamp() const; + [[nodiscard]] bool has_original_commit_timestamp() const noexcept { + return original_commit_timestamp_ != unset_commit_timestamp; + } + [[nodiscard]] std::uint64_t + get_original_commit_timestamp_raw() const noexcept { + return original_commit_timestamp_; + } + + [[nodiscard]] bool has_transaction_length() const noexcept { + return transaction_length_ != unset_transaction_length; + } + [[nodiscard]] std::uint64_t get_transaction_length_raw() const noexcept { + return transaction_length_; + } + + [[nodiscard]] bool has_original_server_version() const noexcept { + return original_server_version_ != unset_server_version; + } + [[nodiscard]] std::uint32_t get_original_server_version_raw() const noexcept { + return original_server_version_; + } + [[nodiscard]] util::semantic_version + get_original_server_version() const noexcept; + [[nodiscard]] std::string get_readable_original_server_version() const; + + [[nodiscard]] bool has_immediate_server_version() const noexcept { + return immediate_server_version_ != unset_server_version; + } + [[nodiscard]] std::uint32_t + get_immediate_server_version_raw() const noexcept { + return immediate_server_version_; + } + [[nodiscard]] util::semantic_version + get_immediate_server_version() const noexcept; + [[nodiscard]] std::string get_readable_immediate_server_version() const; + + [[nodiscard]] bool has_commit_group_ticket() const noexcept { + return commit_group_ticket_ != unset_commit_group_ticket; + } + [[nodiscard]] std::uint64_t get_commit_group_ticket_raw() const noexcept { + return commit_group_ticket_; + } + +private: + static constexpr std::uint64_t unset_commit_timestamp{ + std::numeric_limits::max()}; + static constexpr std::uint64_t unset_transaction_length{ + std::numeric_limits::max()}; + static constexpr std::uint32_t unset_server_version{ + std::numeric_limits::max()}; + static constexpr std::uint64_t unset_commit_group_ticket{ + std::numeric_limits::max()}; + + static constexpr std::size_t commit_timestamp_field_length{7U}; + static constexpr std::size_t server_version_field_length{4U}; + static constexpr std::size_t commit_group_ticket_field_length{8}; + + std::uint64_t immediate_commit_timestamp_{unset_commit_timestamp}; // 0 + std::uint64_t original_commit_timestamp_{unset_commit_timestamp}; // 1 + std::uint64_t transaction_length_{unset_transaction_length}; // 2 + std::uint32_t original_server_version_{unset_server_version}; // 3 + std::uint32_t immediate_server_version_{unset_server_version}; // 4 + std::uint64_t commit_group_ticket_{unset_commit_group_ticket}; // 5 +}; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_GTID_LOG_BODY_IMPL_HPP diff --git a/src/binsrv/event/gtid_log_body_impl_fwd.hpp b/src/binsrv/event/gtid_log_body_impl_fwd.hpp new file mode 100644 index 0000000..8c9f985 --- /dev/null +++ b/src/binsrv/event/gtid_log_body_impl_fwd.hpp @@ -0,0 +1,33 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_GTID_LOG_BODY_IMPL_FWD_HPP +#define BINSRV_EVENT_GTID_LOG_BODY_IMPL_FWD_HPP + +#include + +#include "binsrv/event/code_type.hpp" +#include "binsrv/event/generic_body_fwd.hpp" + +namespace binsrv::event { + +template <> class generic_body_impl; + +std::ostream &operator<<(std::ostream &output, + const generic_body_impl &obj); + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_GTID_LOG_BODY_IMPL_FWD_HPP diff --git a/src/binsrv/event/gtid_log_flag_type.hpp b/src/binsrv/event/gtid_log_flag_type.hpp new file mode 100644 index 0000000..e1cda4f --- /dev/null +++ b/src/binsrv/event/gtid_log_flag_type.hpp @@ -0,0 +1,69 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_GTID_LOG_FLAG_TYPE_HPP +#define BINSRV_EVENT_GTID_LOG_FLAG_TYPE_HPP + +#include "binsrv/event/gtid_log_flag_type_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include + +#include "util/flag_set.hpp" + +namespace binsrv::event { + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +// GTID_LOG event flags copied from +// https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/include/control_events.h#L947 +// https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.h#L956 +// +// https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/include/control_events.h#L1017 +// https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.h#L1027 +// 00000001 = Transaction may have changes logged with SBR +// clang-format off +#define BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_SEQUENCE() \ + BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_MACRO(may_have_sbr, 0x01U) +// clang-format on + +#define BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_MACRO(X, Y) X = Y +// NOLINTNEXTLINE(readability-enum-initial-value,cert-int09-c) +enum class gtid_log_flag_type : std::uint8_t { + BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_SEQUENCE(), + delimiter +}; +#undef BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_MACRO + +inline std::string_view to_string_view(gtid_log_flag_type code) noexcept { + using namespace std::string_view_literals; + using nv_pair = std::pair; +#define BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_MACRO(X, Y) \ + nv_pair { gtid_log_flag_type::X, #X##sv } + static constexpr std::array labels{ + BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_SEQUENCE(), + nv_pair{gtid_log_flag_type::delimiter, ""sv}}; +#undef BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_MACRO + // NOLINTNEXTLINE(llvm-qualified-auto,readability-qualified-auto) + const auto fnd{std::ranges::find(labels, code, &nv_pair::first)}; + return fnd == std::end(labels) ? ""sv : fnd->second; +} +#undef BINSRV_EVENT_GTID_LOG_FLAG_TYPE_XY_SEQUENCE +// NOLINTEND(cppcoreguidelines-macro-usage) + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_GTID_LOG_FLAG_TYPE_HPP diff --git a/src/binsrv/event/flag_type_fwd.hpp b/src/binsrv/event/gtid_log_flag_type_fwd.hpp similarity index 78% rename from src/binsrv/event/flag_type_fwd.hpp rename to src/binsrv/event/gtid_log_flag_type_fwd.hpp index d2883d2..f78e5c2 100644 --- a/src/binsrv/event/flag_type_fwd.hpp +++ b/src/binsrv/event/gtid_log_flag_type_fwd.hpp @@ -13,8 +13,8 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#ifndef BINSRV_EVENT_FLAG_TYPE_FWD_HPP -#define BINSRV_EVENT_FLAG_TYPE_FWD_HPP +#ifndef BINSRV_EVENT_GTID_LOG_FLAG_TYPE_FWD_HPP +#define BINSRV_EVENT_GTID_LOG_FLAG_TYPE_FWD_HPP #include @@ -23,10 +23,10 @@ namespace binsrv::event { // NOLINTNEXTLINE(readability-enum-initial-value,cert-int09-c) -enum class flag_type : std::uint16_t; +enum class gtid_log_flag_type : std::uint8_t; -using flag_set = util::flag_set; +using gtid_log_flag_set = util::flag_set; } // namespace binsrv::event -#endif // BINSRV_EVENT_FLAG_TYPE_FWD_HPP +#endif // BINSRV_EVENT_GTID_LOG_FLAG_TYPE_FWD_HPP diff --git a/src/binsrv/event/gtid_log_post_header_impl.cpp b/src/binsrv/event/gtid_log_post_header_impl.cpp new file mode 100644 index 0000000..c5be83b --- /dev/null +++ b/src/binsrv/event/gtid_log_post_header_impl.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#include "binsrv/event/gtid_log_post_header_impl.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "binsrv/event/code_type.hpp" +#include "binsrv/event/gtid_log_flag_type.hpp" + +#include "binsrv/gtid/common_types.hpp" + +#include "util/byte_span.hpp" +#include "util/byte_span_extractors.hpp" +#include "util/exception_location_helpers.hpp" +#include "util/flag_set.hpp" + +namespace binsrv::event { + +generic_post_header_impl::generic_post_header_impl( + util::const_byte_span portion) { + // TODO: rework with direct member initialization + + /* + https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/include/control_events.h#L918 + https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.h#L927 + + + GTID_FLAGS + 1 byte + 00000001 = Transaction may have changes logged with SBR. + In 5.6, 5.7.0-5.7.18, and 8.0.0-8.0.1, this flag is always set. + Starting in 5.7.19 and 8.0.2, this flag is cleared if the transaction + only contains row events. It is set if any part of the transaction is + written in statement format. + + + SID + 16 byte sequence + UUID representing the SID + + + GNO + 8 byte integer + Group number, second component of GTID. + + + logical clock timestamp typecode + 1 byte integer + The type of logical timestamp used in the logical clock fields. + + + last_committed + 8 byte integer + Store the transaction's commit parent sequence_number + + + sequence_number + 8 byte integer + The transaction's logical timestamp assigned at prepare phase + + */ + + // TODO: initialize size_in_bytes directly based on the sum of fields + // widths instead of this static_assert + static_assert(sizeof flags_ + std::tuple_size_v + + sizeof gno_ + sizeof logical_ts_code_ + + sizeof last_committed_ + sizeof sequence_number_ == + size_in_bytes, + "mismatch in gtid_log_event_post_header::size_in_bytes"); + // make sure we did OK with data members reordering + static_assert( + sizeof *this == + boost::alignment::align_up(size_in_bytes, alignof(decltype(*this))), + "inefficient data member reordering in gtid_log_event_post_header"); + + if (std::size(portion) != size_in_bytes) { + util::exception_location().raise( + "invalid gtid_log event post-header length"); + } + + // https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/src/control_events.cpp#L428 + // https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.cpp#L461 + auto remainder = portion; + util::extract_fixed_int_from_byte_span(remainder, flags_); + util::extract_byte_array_from_byte_span(remainder, uuid_); + util::extract_fixed_int_from_byte_span(remainder, gno_); + if (gno_ < gtid::min_gno || gno_ >= gtid::max_gno) { + util::exception_location().raise( + "invalid gno in gtid_log post-header"); + } + util::extract_fixed_int_from_byte_span(remainder, logical_ts_code_); + if (logical_ts_code_ != expected_logical_ts_code) { + util::exception_location().raise( + "unsupported logical timestamp code in gtid_log post-header"); + } + util::extract_fixed_int_from_byte_span(remainder, last_committed_); + util::extract_fixed_int_from_byte_span(remainder, sequence_number_); +} + +[[nodiscard]] gtid_log_flag_set +generic_post_header_impl::get_flags() const noexcept { + return gtid_log_flag_set{get_flags_raw()}; +} + +[[nodiscard]] std::string +generic_post_header_impl::get_readable_flags() const { + return to_string(get_flags()); +} + +[[nodiscard]] gtid::uuid +generic_post_header_impl::get_uuid() const noexcept { + gtid::uuid result; + const auto &uuid_raw{get_uuid_raw()}; + static_assert(std::tuple_size_v == + boost::uuids::uuid::static_size()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + std::copy_n(reinterpret_cast( + std::data(uuid_raw)), + boost::uuids::uuid::static_size(), std::begin(result)); + return result; +} +[[nodiscard]] std::string +generic_post_header_impl::get_readable_uuid() const { + return boost::uuids::to_string(get_uuid()); +} + +std::ostream & +operator<<(std::ostream &output, + const generic_post_header_impl &obj) { + return output << "flags: " << obj.get_readable_flags() + << ", uuid: " << obj.get_readable_uuid() + << ", gno: " << obj.get_gno() << ", logical_ts_code: " + << static_cast(obj.get_logical_ts_code_raw()) + << ", last_committed: " << obj.get_last_committed_raw() + << ", sequence_number: " << obj.get_sequence_number_raw(); +} + +} // namespace binsrv::event diff --git a/src/binsrv/event/gtid_log_post_header_impl.hpp b/src/binsrv/event/gtid_log_post_header_impl.hpp new file mode 100644 index 0000000..1b906c2 --- /dev/null +++ b/src/binsrv/event/gtid_log_post_header_impl.hpp @@ -0,0 +1,84 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_HPP +#define BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_HPP + +#include "binsrv/event/gtid_log_post_header_impl_fwd.hpp" // IWYU pragma: export + +#include +#include + +#include "binsrv/event/gtid_log_flag_type_fwd.hpp" + +#include "binsrv/gtid/common_types.hpp" + +#include "util/byte_span_fwd.hpp" + +namespace binsrv::event { + +template <> class [[nodiscard]] generic_post_header_impl { +public: + static constexpr std::size_t size_in_bytes{42U}; + + static constexpr std::size_t uuid_length{16U}; + using uuid_storage = std::array; + + // https://github.com/mysql/mysql-server/blob/mysql-8.0.43/libbinlogevents/include/control_events.h#L1091 + // https://github.com/mysql/mysql-server/blob/mysql-8.4.6/libs/mysql/binlog/event/control_events.h#L1202 + static constexpr std::uint8_t expected_logical_ts_code{2U}; + + explicit generic_post_header_impl(util::const_byte_span portion); + + [[nodiscard]] std::uint8_t get_flags_raw() const noexcept { return flags_; } + [[nodiscard]] gtid_log_flag_set get_flags() const noexcept; + [[nodiscard]] std::string get_readable_flags() const; + + [[nodiscard]] const uuid_storage &get_uuid_raw() const noexcept { + return uuid_; + } + [[nodiscard]] gtid::uuid get_uuid() const noexcept; + [[nodiscard]] std::string get_readable_uuid() const; + + [[nodiscard]] std::uint64_t get_gno_raw() const noexcept { return gno_; } + [[nodiscard]] gtid::gno_t get_gno() const noexcept { + return static_cast(get_gno_raw()); + } + + [[nodiscard]] std::uint8_t get_logical_ts_code_raw() const noexcept { + return logical_ts_code_; + } + + [[nodiscard]] std::uint64_t get_last_committed_raw() const noexcept { + return last_committed_; + } + + [[nodiscard]] std::uint64_t get_sequence_number_raw() const noexcept { + return sequence_number_; + } + +private: + // the members are deliberately reordered for better packing + std::uint8_t flags_{}; // 0 + std::uint8_t logical_ts_code_{}; // 3 + uuid_storage uuid_{}; // 1 + std::uint64_t gno_{}; // 2 + std::uint64_t last_committed_{}; // 4 + std::uint64_t sequence_number_{}; // 5 +}; + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_HPP diff --git a/src/binsrv/event/gtid_log_post_header_impl_fwd.hpp b/src/binsrv/event/gtid_log_post_header_impl_fwd.hpp new file mode 100644 index 0000000..0753b09 --- /dev/null +++ b/src/binsrv/event/gtid_log_post_header_impl_fwd.hpp @@ -0,0 +1,34 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_FWD_HPP +#define BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_FWD_HPP + +#include + +#include "binsrv/event/code_type.hpp" +#include "binsrv/event/generic_post_header_fwd.hpp" + +namespace binsrv::event { + +template <> class generic_post_header_impl; + +std::ostream & +operator<<(std::ostream &output, + const generic_post_header_impl &obj); + +} // namespace binsrv::event + +#endif // BINSRV_EVENT_GTID_LOG_POST_HEADER_IMPL_FWD_HPP diff --git a/src/binsrv/event/reader_context.cpp b/src/binsrv/event/reader_context.cpp index 25f9c6f..1f63bcf 100644 --- a/src/binsrv/event/reader_context.cpp +++ b/src/binsrv/event/reader_context.cpp @@ -23,8 +23,8 @@ #include #include "binsrv/event/code_type.hpp" +#include "binsrv/event/common_header_flag_type.hpp" #include "binsrv/event/event.hpp" -#include "binsrv/event/flag_type.hpp" #include "binsrv/event/protocol_traits.hpp" #include "util/conversion_helpers.hpp" @@ -66,8 +66,8 @@ reader_context::process_event_in_initial_state(const event ¤t_event) { const auto &common_header{current_event.get_common_header()}; // in the "initial" state we expect only artificial rotate events - const auto is_artificial{ - common_header.get_flags().has_element(flag_type::artificial)}; + const auto is_artificial{common_header.get_flags().has_element( + common_header_flag_type::artificial)}; const auto is_artificial_rotate{ common_header.get_type_code() == code_type::rotate && is_artificial}; if (!is_artificial_rotate) { @@ -168,8 +168,8 @@ reader_context::process_event_in_format_description_processed_state( assert(state_ == state_type::format_description_processed); const auto &common_header{current_event.get_common_header()}; const auto code{common_header.get_type_code()}; - const auto is_artificial{ - common_header.get_flags().has_element(flag_type::artificial)}; + const auto is_artificial{common_header.get_flags().has_element( + common_header_flag_type::artificial)}; // early return here with "false" return code so that the while loop // in the main 'process_event()' method would repeat processing this diff --git a/src/binsrv/gtid/common_types.hpp b/src/binsrv/gtid/common_types.hpp new file mode 100644 index 0000000..8d32b02 --- /dev/null +++ b/src/binsrv/gtid/common_types.hpp @@ -0,0 +1,34 @@ +// Copyright (c) 2023-2024 Percona and/or its affiliates. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License, version 2.0, +// as published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License, version 2.0, for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef BINSRV_GTID_COMMON_TYPES_HPP +#define BINSRV_GTID_COMMON_TYPES_HPP + +#include +#include + +#include + +namespace binsrv::gtid { + +using gno_t = std::int64_t; +inline constexpr gno_t min_gno{1LL}; +inline constexpr gno_t max_gno{std::numeric_limits::max()}; + +using uuid = boost::uuids::uuid; + +} // namespace binsrv::gtid + +#endif // BINSRV_GTID_COMMON_TYPES_HPP diff --git a/src/util/byte_span_extractors.hpp b/src/util/byte_span_extractors.hpp index b02be7d..272227d 100644 --- a/src/util/byte_span_extractors.hpp +++ b/src/util/byte_span_extractors.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -30,20 +31,42 @@ namespace util { template requires std::integral || std::same_as -void extract_fixed_int_from_byte_span(util::const_byte_span &remainder, - T &value) noexcept { - assert(std::size(remainder) >= sizeof value); - T value_in_network_format; - std::memcpy(&value_in_network_format, std::data(remainder), sizeof(T)); - // A fixed-length unsigned integer stores its value in a series of - // bytes with the least significant byte first. - // TODO: in c++23 use std::byteswap() - value = boost::endian::little_to_native(value_in_network_format); - remainder = remainder.subspan(sizeof(T)); +void extract_fixed_int_from_byte_span( + util::const_byte_span &remainder, T &value, + std::size_t bytes_to_extract = sizeof(T)) noexcept { + assert(bytes_to_extract != 0U); + assert(bytes_to_extract <= sizeof(T)); + assert(std::size(remainder) >= bytes_to_extract); + if constexpr (sizeof(T) != 1U) { + T value_in_network_format{}; // zero-initialized + std::memcpy(&value_in_network_format, std::data(remainder), + bytes_to_extract); + // A fixed-length unsigned integer stores its value in a series of + // bytes with the least significant byte first. + // TODO: in c++23 use std::byteswap() + value = boost::endian::little_to_native(value_in_network_format); + } else { + // TODO: in c++23 change use std::to_underlying(*std::data(remainder)) + using underlying_type = std::underlying_type_t; + value = static_cast(static_cast(*std::data(remainder))); + } + remainder = remainder.subspan(bytes_to_extract); } template - requires(std::integral || std::same_as) && (sizeof(T) == 1U) + requires std::integral || std::same_as +[[nodiscard]] bool extract_fixed_int_from_byte_span_checked( + util::const_byte_span &remainder, T &value, + std::size_t bytes_to_extract = sizeof(T)) noexcept { + if (bytes_to_extract > std::size(remainder)) { + return false; + } + extract_fixed_int_from_byte_span(remainder, value, bytes_to_extract); + return true; +} + +template + requires((std::integral && sizeof(T) == 1U) || std::same_as) void extract_byte_span_from_byte_span(util::const_byte_span &remainder, std::span storage_span) noexcept { assert(std::size(remainder) >= storage_span.size()); @@ -53,8 +76,20 @@ void extract_byte_span_from_byte_span(util::const_byte_span &remainder, remainder = remainder.subspan(storage_span.size()); } +template + requires((std::integral && sizeof(T) == 1U) || std::same_as) +[[nodiscard]] bool +extract_byte_span_from_byte_span_checked(util::const_byte_span &remainder, + std::span storage_span) noexcept { + if (storage_span.size() > std::size(remainder)) { + return false; + } + extract_byte_span_from_byte_span(remainder, storage_span); + return true; +} + template - requires(std::integral || std::same_as) && (sizeof(T) == 1U) + requires((std::integral && sizeof(T) == 1U) || std::same_as) void extract_byte_array_from_byte_span(util::const_byte_span &remainder, std::array &storage) noexcept { assert(std::size(remainder) >= N); @@ -63,6 +98,70 @@ void extract_byte_array_from_byte_span(util::const_byte_span &remainder, remainder = remainder.subspan(N); } +template + requires((std::integral && sizeof(T) == 1U) || std::same_as) +[[nodiscard]] bool +extract_byte_array_from_byte_span_checked(util::const_byte_span &remainder, + std::array &storage) noexcept { + if (N > std::size(remainder)) { + return false; + } + extract_byte_array_from_byte_span(remainder, storage); + return true; +} + +template + requires(std::integral && sizeof(T) == sizeof(std::uint64_t)) +[[nodiscard]] bool +extract_packed_int_from_byte_span_checked(util::const_byte_span &remainder, + T &value) noexcept { + util::const_byte_span local_remainder{remainder}; + std::uint8_t first_byte{}; + if (!util::extract_fixed_int_from_byte_span_checked(local_remainder, + first_byte)) { + return false; + } + static constexpr unsigned char max_marker{251U}; + static constexpr unsigned char double_marker{252U}; + static constexpr unsigned char triple_marker{253U}; + static constexpr unsigned char octuple_marker{254U}; + static constexpr unsigned char forbidden_marker{255U}; + + static constexpr std::size_t double_size{2U}; + static constexpr std::size_t triple_size{3U}; + static constexpr std::size_t octuple_size{8U}; + + std::uint64_t unpacked{}; + bool result{true}; + switch (first_byte) { + case max_marker: + unpacked = std::numeric_limits::max(); + break; + case double_marker: + result = util::extract_fixed_int_from_byte_span_checked( + local_remainder, unpacked, double_size); + break; + case triple_marker: + result = util::extract_fixed_int_from_byte_span_checked( + local_remainder, unpacked, triple_size); + break; + case octuple_marker: + result = util::extract_fixed_int_from_byte_span_checked( + local_remainder, unpacked, octuple_size); + break; + [[unlikely]] case forbidden_marker: + result = false; + break; + [[likely]] default: + unpacked = first_byte; + } + if (result) { + remainder = local_remainder; + value = unpacked; + } + return result; +} + } // namespace util #endif // UTIL_BYTE_SPAN_EXTRACTORS_HPP