diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 0000000..bd86895 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1 @@ +extra/mysql_protocol/mysqlrouter/* diff --git a/.clang-tidy b/.clang-tidy index 09f2337..dca489a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -13,4 +13,5 @@ Checks: " -llvm-header-guard " WarningsAsErrors: '*' -HeaderFilterRegex: '' +HeaderFilterRegex: '.*' +ExcludeHeaderFilterRegex: 'mysqlrouter/.*' diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 5ed6678..01a2e24 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -263,10 +263,14 @@ jobs: if: matrix.config.run_clang_tidy run: clang-tidy-20 --version + - name: Dump Clang Tidy Config + if: matrix.config.run_clang_tidy + run: clang-tidy-20 --dump-config --config-file=${{github.workspace}}/src/.clang-tidy + - name: Clang Tidy if: matrix.config.run_clang_tidy # Run Clang Tidy - run: run-clang-tidy-20 -header-filter=.* -j=${{steps.cpu-cores.outputs.count}} -use-color -p=${{github.workspace}}/src-build-${{matrix.config.label}} + run: run-clang-tidy-20 -config-file=${{github.workspace}}/src/.clang-tidy -j=${{steps.cpu-cores.outputs.count}} -use-color -p=${{github.workspace}}/src-build-${{matrix.config.label}} - name: Application version working-directory: ${{github.workspace}}/src-build-${{matrix.config.label}} diff --git a/CMakeLists.txt b/CMakeLists.txt index d0f1c40..7eb6928 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ if(WITH_ASAN) target_link_options(binlog_server_compiler_flags INTERFACE "-fsanitize=address") endif() -find_package(Boost 1.90.0 EXACT REQUIRED COMPONENTS url json) +find_package(Boost 1.90.0 EXACT REQUIRED COMPONENTS url json asio) find_package(MySQL REQUIRED) @@ -536,6 +536,74 @@ set_target_properties(binlog_server PROPERTIES CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) +set(mysql_harness_source_files + extra/mysql_protocol/mysql/harness/net_ts/buffer.h + + extra/mysql_protocol/mysql/harness/stdx/bit.h + extra/mysql_protocol/mysql/harness/stdx/expected.h + extra/mysql_protocol/mysql/harness/stdx/flags.h + extra/mysql_protocol/mysql/harness/stdx/ranges.h + extra/mysql_protocol/mysql/harness/stdx/type_traits.h +) + +set(mysql_protocol_source_files + extra/mysql_protocol/mysqlrouter/classic_protocol.h + extra/mysql_protocol/mysqlrouter/classic_protocol_binary.h + extra/mysql_protocol/mysqlrouter/classic_protocol_clone.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_base.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_binary.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_clone.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_error.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_frame.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_message.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_session_track.h + extra/mysql_protocol/mysqlrouter/classic_protocol_codec_wire.h + extra/mysql_protocol/mysqlrouter/classic_protocol_constants.h + extra/mysql_protocol/mysqlrouter/classic_protocol_frame.h + extra/mysql_protocol/mysqlrouter/classic_protocol_message.h + extra/mysql_protocol/mysqlrouter/classic_protocol_session_track.h + extra/mysql_protocol/mysqlrouter/classic_protocol_wire.h) + +set(minimysql_source_files + src/minimysql/caching_sha2_password_authenticator.hpp + src/minimysql/caching_sha2_password_authenticator.cpp + src/minimysql/connection_context_fwd.hpp + src/minimysql/connection_context.hpp + src/minimysql/connection_context.cpp + + src/minimysql_app.cpp +) + +add_executable(minimysql_server ${minimysql_source_files} ${mysql_protocol_source_files} ${mysql_harness_source_files}) + +# GCC 14 may report a false-positive mismatched allocation/deallocation +# warning in boost::asio coroutine machinery (awaitable_frame) used in +# minimysql_app.cpp. +# Also, there are some false-positive null-dereference warnings in boost::asio +# io_context. +# Keep -Werror globally and suppress only this source file. +set_source_files_properties(src/minimysql_app.cpp PROPERTIES + COMPILE_OPTIONS + "$<$,$,14>>:-Wno-error=mismatched-new-delete;-Wno-error=null-dereference>" +) + +target_compile_definitions(minimysql_server PRIVATE BOOST_ASIO_NO_DEPRECATED) +target_link_libraries(minimysql_server + PRIVATE + binlog_server_compiler_flags + Boost::headers Boost::asio + OpenSSL::Crypto +) + +target_include_directories(minimysql_server PRIVATE "${PROJECT_SOURCE_DIR}/extra/mysql_protocol") + +# it is not possible to propagate CXX_EXTENSIONS and CXX_STANDARD_REQUIRED +# via interface library (binlog_server_compiler_flags) +set_target_properties(minimysql_server PROPERTIES + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) include(CTest) if(BUILD_TESTING) diff --git a/extra/mysql_protocol/mysql/harness/net_ts/buffer.h b/extra/mysql_protocol/mysql/harness/net_ts/buffer.h new file mode 100644 index 0000000..cbfb93e --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/net_ts/buffer.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_NET_TS_BUFFER_H +#define MYSQL_HARNESS_NET_TS_BUFFER_H + +#include + +namespace net { + +using boost::asio::buffer; +using boost::asio::buffer_size; +using boost::asio::const_buffer; +using boost::asio::mutable_buffer; + +} // namespace net + +#endif // MYSQL_HARNESS_NET_TS_BUFFER_H diff --git a/extra/mysql_protocol/mysql/harness/stdx/bit.h b/extra/mysql_protocol/mysql/harness/stdx/bit.h new file mode 100644 index 0000000..dc116b1 --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/stdx/bit.h @@ -0,0 +1,37 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_STDX_BIT_H +#define MYSQL_HARNESS_STDX_BIT_H + +#include + +namespace stdx { + +using std::byteswap; + +} // namespace stdx + +#endif // MYSQL_HARNESS_STDX_BIT_H diff --git a/extra/mysql_protocol/mysql/harness/stdx/expected.h b/extra/mysql_protocol/mysql/harness/stdx/expected.h new file mode 100644 index 0000000..c6ef7ef --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/stdx/expected.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_STDX_EXPECTED_H +#define MYSQL_HARNESS_STDX_EXPECTED_H + +#include + +namespace stdx { + +using std::expected; +using std::unexpected; + +} // namespace stdx + +#endif // MYSQL_HARNESS_STDX_EXPECTED_H diff --git a/extra/mysql_protocol/mysql/harness/stdx/flags.h b/extra/mysql_protocol/mysql/harness/stdx/flags.h new file mode 100644 index 0000000..89e2d74 --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/stdx/flags.h @@ -0,0 +1,326 @@ +/* + Copyright (c) 2021, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_STDX_FLAGS_H +#define MYSQL_HARNESS_STDX_FLAGS_H + +#include // popcount +#include +#include + +#include "mysql/harness/stdx/type_traits.h" // is_scoped_enum_v + +namespace stdx { + +// tags a class as flags-class +template +struct is_flags : public std::false_type {}; + +template inline constexpr bool is_flags_v = is_flags::value; + +/** + * a type-safe flags type. + * + * # abstract + * + * Using flags in the in C++ isn't very ergonimic: + * + * 1. using plain C-enum's isn't typesafe + * 2. using std::bitset requires bit-positions to set/reset/test them + * 3. scoped-enums (enum class) doesn't have operaters + * + * For ease of use it should be possible to have a flags-type which + * allows the type-safe operations: + * + * - flags = flags & flag + * - flags &= flag + * - flags = flags | flag + * - flags |= flag + * - flags = flags ^ flag + * - flags ^= flag + * - flags = ~flag + * + * - flag | flag -> flags + * - flag & flag -> flags + * - flag ^ flag -> flags + * + * # example + * + * @code{.cpp} + * // underlying scoped-enum + * enum class somebits { + * bit0 = 1 << 0, + * bit1 = 1 << 1, + * }; + * + * // activate stdx::flags support for the scoped-enum + * namespace stdx { + * template<> + * struct is_flags : std::true_type {} + * } + * + * { // default-construct + assignment + * stdx::flags someflags; + * + * someflags = somebits::bit0 | somebits::bit1; + * + * // get the underlying bitvalue. + * std::cerr << someflags.underlying_value() << "\n"; + * } + * + * { // direct initialization from enum + * stdx::flags someflags{somebits::bit0 | somebits::bit1}; + * } + * + * { // testing if bit set + * stdx::flags someflags{somebits::bit0 | somebits::bit1}; + * + * if (someflags & somebits::bit1) {} // true + * } + * + * { // setting raw value + * stdx::flags someflags; + * someflags.underlying_value(3); + * + * if (someflags & somebits::bit0) {} // true + * if (someflags & somebits::bit1) {} // true + * } + * @endcode + * + * @tparam E a scoped enum that is tagged with stdx::is_flags. + */ +template class flags { +public: + static_assert(stdx::is_scoped_enum_v, + "flags, E must be a scoped enum type"); + static_assert(is_flags_v, + "flags, E must be declared as flags-type via:" + "namespace stdx { template<> struct is_flags<...> : " + "std::true_type {};} "); + + //!< type of the wrapped enum. + using enum_type = std::decay_t; + //!< underlying integer type of the enum_type. + using underlying_type = std::underlying_type_t; + //!< implementation type of the underlying type. + using impl_type = std::make_unsigned_t; + using size_type = std::size_t; + + /** + * default constructor. + * + * initializes underlying_value to 0 (no-bits-set). + */ + constexpr flags() = default; + + constexpr flags(const flags &other) = default; //!< copy constructor. + constexpr flags(flags &&other) = default; //!< move constructor + constexpr flags &operator=(const flags &other) = default; //!< copy assignment + constexpr flags &operator=(flags &&other) = default; //!< move assignment + + constexpr ~flags() = default; + + /** + * converting constructor from enum_type. + */ + // NOLINTNEXTLINE(hicpp-explicit-conversions) + constexpr flags(enum_type value) : v_{static_cast(value)} {} + + /** + * check if any bit is set. + * + * @retval true at least one bit is set. + * @retval false no bit is set. + */ + // NOLINTNEXTLINE(hicpp-explicit-conversions) + constexpr operator bool() const noexcept { return v_ != 0; } + + /** + * check if no bit is set. + * + * @retval true no bit is set. + * @retval false at least one bit is set. + */ + constexpr bool operator!() const noexcept { return !v_; } + + /** + * negation of all bits. + * + * ~flags -> flags. + */ + constexpr flags operator~() const noexcept { + return flags{static_cast(~v_)}; + } + + /** + * bit-or. + * + * flags |= flags. + */ + constexpr flags &operator|=(const flags &other) noexcept { + v_ |= other.v_; + return *this; + } + + /** + * bit-and. + * + * flags &= flags. + */ + constexpr flags &operator&=(const flags &other) noexcept { + v_ &= other.v_; + return *this; + } + + /** + * bit-xor. + * + * flag ^= flag + */ + constexpr flags &operator^=(const flags &other) noexcept { + v_ ^= other.v_; + return *this; + } + + /** + * bit-and. + * + * flags & flags -> flags + */ + friend constexpr flags operator&(flags left, flags right) noexcept { + return flags{static_cast(left.v_ & right.v_)}; + } + + /** + * bit-or. + * + * flags | flags -> flags + */ + friend constexpr flags operator|(flags left, flags right) noexcept { + return flags{static_cast(left.v_ | right.v_)}; + } + + /** + * bit-xor. + * + * flags ^ flags -> flags + */ + friend constexpr flags operator^(flags left, flags right) noexcept { + return flags{static_cast(left.v_ ^ right.v_)}; + } + + /** + * set underlying value. + * + * @param value underlying value to set. + */ + constexpr void underlying_value(underlying_type value) noexcept { + v_ = value; + } + + /** + * get underlying value. + * + * @return underlying value. + */ + [[nodiscard]] constexpr underlying_type underlying_value() const noexcept { + return v_; + } + + /** + * count bits set to true. + * + * @sa size() + * + * @return bits set to true. + */ + [[nodiscard]] constexpr size_type count() const noexcept { + return std::popcount(v_); + } + + /** + * returns number of bits the flag-type can hold. + * + * @sa count() + * + * @returns number of bits the flag-type can hold. + */ + [[nodiscard]] constexpr size_type size() const noexcept { + return CHAR_BIT * sizeof(underlying_type); + } + + /** + * reset all flags to 0. + */ + constexpr void reset() { v_ = 0; } + +private: + constexpr explicit flags(impl_type value) noexcept : v_{value} {} + + impl_type v_{}; +}; + +} // namespace stdx + +/** + * bit-or. + * + * E | E -> flags; + * + * @return a flags of bit-or(e1, e2). + */ +template + requires stdx::is_flags_v +constexpr stdx::flags operator|(E left, E right) noexcept { + return stdx::flags(left) | right; +} + +/** + * bit-and. + * + * E & E -> flags; + * + * @return a flags of bit-and(e1, e2). + */ +template + requires stdx::is_flags_v +constexpr stdx::flags operator&(E left, E right) noexcept { + return stdx::flags(left) & right; +} + +/** + * bit-xor. + * + * E ^ E -> flags; + * + * @return a flags of bit-xor(e1, e2). + */ +template + requires stdx::is_flags_v +constexpr stdx::flags operator^(E left, E right) noexcept { + return stdx::flags(left) ^ right; +} + +#endif // MYSQL_HARNESS_STDX_FLAGS_H diff --git a/extra/mysql_protocol/mysql/harness/stdx/ranges.h b/extra/mysql_protocol/mysql/harness/stdx/ranges.h new file mode 100644 index 0000000..b01be3d --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/stdx/ranges.h @@ -0,0 +1,160 @@ +/* + Copyright (c) 2022, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_STDX_RANGES_ENUMERATE_H +#define MYSQL_HARNESS_STDX_RANGES_ENUMERATE_H + +// +// From C++23's P2164 +// +// http://wg21.link/p2164 + +#include +#include +#include +#include // declval, forward + +namespace stdx::ranges { + +// TODO: change the content of this this file to "using +// std::ranges::views::enumerate" when switching to clang-23 + +/** + * enumerate_view over a range. + * + * @note only implements the const-iterator parts. + * + * @tparam V a range to enumerate + */ +template class enumerate_view { +private: + using Base = V; + + Base base_ = {}; + + template class iterator; + +public: + using value_type = std::iter_value_t>; + + constexpr enumerate_view() = default; + + constexpr explicit enumerate_view(V base) : base_(std::forward(base)) {} + + constexpr auto begin() const { return iterator{std::begin(base_), 0}; } + + constexpr auto end() const { return iterator{std::end(base_), 0}; } +}; + +template enumerate_view(R &&) -> enumerate_view; + +template template class enumerate_view::iterator { +private: + using Base = std::conditional_t; + +public: + using iterator_category = std::input_iterator_tag; + + using index_type = size_t; + using reference = + std::tuple>; + using value_type = std::tuple>; + + constexpr explicit iterator(std::ranges::iterator_t current, + index_type pos) + : pos_{pos}, current_{std::move(current)} {} + + constexpr bool operator!=(const iterator &other) const { + return current_ != other.current_; + } + + constexpr iterator &operator++() { + ++pos_; + ++current_; + + return *this; + } + + constexpr decltype(auto) operator*() const { + return reference{pos_, *current_}; + } + +private: + index_type pos_; + + std::ranges::iterator_t current_; +}; + +namespace views { +/* + * an iterator that wraps an iterable and returns a counter and the + * deref'ed wrapped iterable. + * + * @tparam T a iterable + * + * @code + * for (auto [ndx, vc]: enumerate(std::vector{1, 23, 42})) { + * std::cerr << "[" << ndx << "] " << v << "\n"; + * } + * + * // [0] 1 + * // [1] 23 + * // [2] 42 + * @endcode + * + * modelled after P2164 from C++23, but implemented for C++17 (aka without + * ranges and views) + */ +template ())), + class = decltype(std::end(std::declval()))> +constexpr auto enumerate(T &&iterable) { + return enumerate_view{std::forward(iterable)}; +} +} // namespace views + +/** + * Checks if a container contains a specified element. Implements + * https://en.cppreference.com/w/cpp/algorithm/ranges/contains + * + * @param container The container in which to search for the needle. + * @param needle The element to search for in the container. + * @return `true` if the needle is found in the container, `false` otherwise. + */ +template + requires std::convertible_to +bool contains(const Range &container, const T &needle) { + return std::find(container.begin(), container.end(), needle) != + container.end(); +} + +} // namespace stdx::ranges + +namespace stdx { + +namespace views = ranges::views; + +} // namespace stdx + +#endif // MYSQL_HARNESS_STDX_RANGES_ENUMERATE_H diff --git a/extra/mysql_protocol/mysql/harness/stdx/type_traits.h b/extra/mysql_protocol/mysql/harness/stdx/type_traits.h new file mode 100644 index 0000000..c32bcdd --- /dev/null +++ b/extra/mysql_protocol/mysql/harness/stdx/type_traits.h @@ -0,0 +1,41 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_HARNESS_STDX_TYPE_TRAITS_H +#define MYSQL_HARNESS_STDX_TYPE_TRAITS_H + +#include + +namespace stdx { + +using std::is_scoped_enum; +using std::is_scoped_enum_v; + +using std::type_identity; +using std::type_identity_t; + +} // namespace stdx + +#endif // MYSQL_HARNESS_STDX_TYPE_TRAITS_H diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol.h b/extra/mysql_protocol/mysqlrouter/classic_protocol.h new file mode 100644 index 0000000..bb746da --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol.h @@ -0,0 +1,35 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_H_ + +#include "mysqlrouter/classic_protocol_codec.h" +#include "mysqlrouter/classic_protocol_constants.h" +#include "mysqlrouter/classic_protocol_frame.h" +#include "mysqlrouter/classic_protocol_message.h" +#include "mysqlrouter/classic_protocol_wire.h" + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_binary.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_binary.h new file mode 100644 index 0000000..1a1ca7d --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_binary.h @@ -0,0 +1,372 @@ +/* + Copyright (c) 2023, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_BINARY_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_BINARY_H_ + +#include // size_t +#include // uint8_t +#include // error_code +#include // move + +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { +namespace borrowable { +namespace binary { + +template +using string_type = std::conditional_t; + +/** + * base type of all binary scalar value types. + */ +template +class TypeBase { + public: + using value_type = T; + + constexpr TypeBase(value_type val) : v_(val) {} + + [[nodiscard]] constexpr value_type value() const { return v_; } + + friend bool operator==(const TypeBase &lhs, const TypeBase &rhs) { + return lhs.value() == rhs.value(); + } + + friend bool operator!=(const TypeBase &lhs, const TypeBase &rhs) { + return !(lhs == rhs); + } + + private: + value_type v_; +}; + +/** + * POD base-type for Datetime, Timestamp, Date. + */ +class DatetimeBase { + public: + DatetimeBase() = default; + + constexpr DatetimeBase(uint16_t year, uint8_t month, uint8_t day, + uint8_t hour, uint8_t minute, uint8_t second, + uint32_t microsecond = 0) + : year_(year), + month_(month), + day_(day), + hour_(hour), + minute_(minute), + second_(second), + microsecond_(microsecond) {} + + constexpr DatetimeBase(uint16_t year, uint8_t month, uint8_t day) + : DatetimeBase(year, month, day, 0, 0, 0, 0) {} + + [[nodiscard]] constexpr uint16_t year() const { return year_; } + [[nodiscard]] constexpr uint8_t month() const { return month_; } + [[nodiscard]] constexpr uint8_t day() const { return day_; } + [[nodiscard]] constexpr uint8_t hour() const { return hour_; } + [[nodiscard]] constexpr uint8_t minute() const { return minute_; } + [[nodiscard]] constexpr uint8_t second() const { return second_; } + [[nodiscard]] constexpr uint32_t microsecond() const { return microsecond_; } + + friend bool operator==(const DatetimeBase &lhs, const DatetimeBase &rhs) { + return lhs.year() == rhs.year() && // + lhs.month() == rhs.month() && // + lhs.day() == rhs.day() && // + lhs.hour() == rhs.hour() && // + lhs.minute() == rhs.minute() && // + lhs.second() == rhs.second() && // + lhs.microsecond() == rhs.microsecond(); + } + + friend bool operator!=(const DatetimeBase &lhs, const DatetimeBase &rhs) { + return !(lhs == rhs); + } + + private: + uint16_t year_{}; + uint8_t month_{}; + uint8_t day_{}; + uint8_t hour_{}; + uint8_t minute_{}; + uint8_t second_{}; + uint32_t microsecond_{}; +}; + +class DateTime : public DatetimeBase { + public: + using DatetimeBase::DatetimeBase; +}; + +class Timestamp : public DatetimeBase { + public: + using DatetimeBase::DatetimeBase; +}; + +class Date : public DatetimeBase { + public: + using DatetimeBase::DatetimeBase; +}; + +class Time { + public: + constexpr Time() = default; + + constexpr Time(bool is_negative, uint32_t days, uint8_t hour, uint8_t minute, + uint8_t second, uint32_t microsecond = 0) + : is_negative_(is_negative), + days_(days), + hour_(hour), + minute_(minute), + second_(second), + microsecond_(microsecond) {} + + [[nodiscard]] constexpr bool is_negative() const { return is_negative_; } + [[nodiscard]] constexpr uint32_t days() const { return days_; } + [[nodiscard]] constexpr uint8_t hour() const { return hour_; } + [[nodiscard]] constexpr uint8_t minute() const { return minute_; } + [[nodiscard]] constexpr uint8_t second() const { return second_; } + [[nodiscard]] constexpr uint32_t microsecond() const { return microsecond_; } + + friend bool operator==(const Time &lhs, const Time &rhs) { + return lhs.days() == rhs.days() && // + lhs.hour() == rhs.hour() && // + lhs.minute() == rhs.minute() && // + lhs.second() == rhs.second() && // + lhs.microsecond() == rhs.microsecond() && // + lhs.is_negative() == rhs.is_negative(); + } + + friend bool operator!=(const Time &lhs, const Time &rhs) { + return !(lhs == rhs); + } + + private: + bool is_negative_{}; + uint32_t days_{}; + uint8_t hour_{}; + uint8_t minute_{}; + uint8_t second_{}; + uint32_t microsecond_{}; +}; + +class LongLong : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Long : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Int24 : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Short : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Year : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Tiny : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Double : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +class Float : public TypeBase { + public: + using TypeBase::TypeBase; +}; + +template +class String : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class VarString : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Varchar : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Json : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Blob : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class TinyBlob : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class MediumBlob : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class LongBlob : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Enum : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Set : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Decimal : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class NewDecimal : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Bit : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +template +class Geometry : public TypeBase> { + public: + using TypeBase>::TypeBase; +}; + +class Null {}; + +} // namespace binary +} // namespace borrowable + +namespace borrowed { +namespace binary { +using Tiny = borrowable::binary::Tiny; +using Short = borrowable::binary::Short; +using Int24 = borrowable::binary::Int24; +using Long = borrowable::binary::Long; +using LongLong = borrowable::binary::LongLong; +using Float = borrowable::binary::Float; +using Double = borrowable::binary::Double; +using Year = borrowable::binary::Year; +using Time = borrowable::binary::Time; +using Date = borrowable::binary::Date; +using DateTime = borrowable::binary::DateTime; +using Timestamp = borrowable::binary::Timestamp; +using String = borrowable::binary::String; +using VarString = borrowable::binary::VarString; +using Varchar = borrowable::binary::Varchar; +using TinyBlob = borrowable::binary::TinyBlob; +using Blob = borrowable::binary::Blob; +using MediumBlob = borrowable::binary::MediumBlob; +using LongBlob = borrowable::binary::LongBlob; +using Enum = borrowable::binary::Enum; +using Set = borrowable::binary::Set; +using Decimal = borrowable::binary::Decimal; +using NewDecimal = borrowable::binary::NewDecimal; +using Json = borrowable::binary::Json; +using Geometry = borrowable::binary::Geometry; +using Bit = borrowable::binary::Bit; +using Null = borrowable::binary::Null; +} // namespace binary +} // namespace borrowed + +namespace binary { +using Tiny = borrowable::binary::Tiny; +using Short = borrowable::binary::Short; +using Int24 = borrowable::binary::Int24; +using Long = borrowable::binary::Long; +using LongLong = borrowable::binary::LongLong; +using Float = borrowable::binary::Float; +using Double = borrowable::binary::Double; +using Year = borrowable::binary::Year; +using Time = borrowable::binary::Time; +using Date = borrowable::binary::Date; +using DateTime = borrowable::binary::DateTime; +using Timestamp = borrowable::binary::Timestamp; +using String = borrowable::binary::String; +using VarString = borrowable::binary::VarString; +using Varchar = borrowable::binary::Varchar; +using TinyBlob = borrowable::binary::TinyBlob; +using Blob = borrowable::binary::Blob; +using MediumBlob = borrowable::binary::MediumBlob; +using LongBlob = borrowable::binary::LongBlob; +using Enum = borrowable::binary::Enum; +using Set = borrowable::binary::Set; +using Decimal = borrowable::binary::Decimal; +using NewDecimal = borrowable::binary::NewDecimal; +using Json = borrowable::binary::Json; +using Geometry = borrowable::binary::Geometry; +using Bit = borrowable::binary::Bit; +using Null = borrowable::binary::Null; +} // namespace binary +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_clone.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_clone.h new file mode 100644 index 0000000..f5f50d7 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_clone.h @@ -0,0 +1,88 @@ +/* + Copyright (c) 2021, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CLONE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CLONE_H_ + +#include // uint32_t +#include + +namespace classic_protocol::clone { + +struct Locator { + uint8_t storage_engine_type; + std::vector locator; +}; + +namespace client { +// negotiate clone protocol. +// +// response: server::Ok, server::Error +class Init { + public: + uint32_t protocol_version; + uint32_t ddl_timeout; + + std::vector locators; +}; + +class Attach : public Init {}; +class Reinit : public Init {}; + +// no content +class Execute {}; + +class Ack { + public: + uint32_t error_number; + + Locator locator; + + std::vector descriptor; +}; +// no content +class Exit {}; +} // namespace client + +namespace server { +class Locators { + public: + uint32_t protocol_version; + + std::vector locators; +}; +class DataDescriptor { + public: + uint8_t storage_engine_type; + + uint8_t locator_ndx; +}; +class Data {}; +class Complete {}; +class Error {}; +} // namespace server +} // namespace classic_protocol::clone + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec.h new file mode 100644 index 0000000..b5fb64f --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec.h @@ -0,0 +1,38 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_H_ + +// convenience header for all codec headers + +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_codec_frame.h" +#include "mysqlrouter/classic_protocol_codec_message.h" +#include "mysqlrouter/classic_protocol_codec_session_track.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_base.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_base.h new file mode 100644 index 0000000..af70065 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_base.h @@ -0,0 +1,395 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_BASE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_BASE_H_ + +#include // size_t +#include // uint8_t +#include +#include // error_code +#include +#include // move + +#include "mysql/harness/net_ts/buffer.h" +#include "mysql/harness/stdx/bit.h" +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_constants.h" + +namespace classic_protocol { + +// bytes needed to encode x bits +// +// bits | bytes +// -----+------ +// 0 | 0 +// 1 | 1 +// ... | ... +// 8 | 1 +// 9 | 2 +// ... | ... +// +constexpr size_t bytes_per_bits(size_t bits) { return (bits + 7) / 8; } + +static_assert(bytes_per_bits(0) == 0); +static_assert(bytes_per_bits(1) == 1); +static_assert(bytes_per_bits(8) == 1); +static_assert(bytes_per_bits(9) == 2); + +/** + * Codec for a type. + * + * requirements for T: + * - size_t size() + * - stdx::expected encode(net::mutable_buffer); + * - static stdx::expected decode(buffer_sequence, + * capabilities); + */ +template +class Codec; + +/** + * encode a message into a dynamic buffer. + * + * @param v message to encode + * @param caps protocol capabilities + * @param dyn_buffer dynamic buffer to write into + * @returns number of bytes written into dynamic buffer or std::error_code on + * error + */ +template +stdx::expected encode(const T &v, + capabilities::value_type caps, + DynamicBuffer &&dyn_buffer) { + // static_assert(net::is_dynamic_buffer::value, + // "dyn_buffer MUST be a DynamicBuffer"); + + Codec codec(v, caps); + + const auto orig_size = dyn_buffer.size(); + const auto codec_size = codec.size(); + + // reserve some space to write into + dyn_buffer.grow(codec_size); + + const auto res = codec.encode(dyn_buffer.data(orig_size, codec_size)); + if (!res) { + dyn_buffer.shrink(codec_size); + return res; + } + + dyn_buffer.shrink(codec_size - res.value()); + + return res; +} + +/** + * decode a message from a buffer. + * + * @param buffer buffer to read from + * @param caps protocol capabilities + * @tparam T the message class + * @returns number of bytes read from 'buffers' and a T on success, or + * std::error_code on error + */ +template +stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + return Codec::decode(buffer, caps); +} + +/** + * decode a message from a buffer. + * + * @param buffer buffer to read from + * @param caps protocol capabilities + * @param args arguments that shall be forwarded to T's decode() + * @tparam T the message class + * @tparam Args Types of the extra arguments to be forwarded to T's decode() + * function. + * @returns number of bytes read from 'buffers' and a T on success, or + * std::error_code on error + */ +template +stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps, + Args &&...args) { + return Codec::decode(buffer, caps, std::forward(args)...); +} + +namespace impl { + +/** + * Generator of decoded Types of a buffer. + * + * - .step() + */ +class DecodeBufferAccumulator { + public: + using buffer_type = net::const_buffer; + using result_type = stdx::expected; + + /** + * construct a DecodeBufferAccumulator. + * + * @param buffer a net::const_buffer + * @param caps classic-protocol capabilities + * @param consumed bytes to skip from the buffers + */ + DecodeBufferAccumulator(const net::const_buffer &buffer, + capabilities::value_type caps, size_t consumed = 0) + : buffer_(buffer), caps_(caps), consumed_(consumed) {} + + /** + * decode a Type from the buffer sequence. + * + * if it succeeds, moves position in buffer forward and returns + * decoded Type, otherwise returns error and updates the global error-code in + * result() + * + * 'sz' is unlimited, the whole rest of the current buffer + * is passed to the decoder. + * + * If not, a slice of size 'sz' is taken. If there isn't at least 'sz' bytes + * in the buffer, it fails. + * + * @param sz limits the size of the current buffer. + */ + template + stdx::expected::value_type, std::error_code> step( + size_t sz = std::numeric_limits::max()) { + if (!res_) return stdx::unexpected(res_.error()); + + auto step_res = step_(sz); + + // capture the first failure + if (!step_res) res_ = stdx::unexpected(step_res.error()); + + return step_res; + } + + /** + * try decoding a Type from the buffer sequence. + * + * if it succeeds, moves position in buffer-sequence forward and returns + * decoded Type, otherwise returns error and does NOT update the global + * error-code in result() + */ + template + stdx::expected::value_type, std::error_code> try_step( + size_t sz = std::numeric_limits::max()) { + if (!res_) return stdx::unexpected(res_.error()); + + return step_(sz); + } + + /** + * get result of the step(). + * + * if a step() failed, result is the error-code of the first failed step() + * + * @returns consumed bytes by all steps(), or error of first failed step() + */ + result_type result() const { + if (!res_) return res_; + + return consumed_; + } + + private: + template + stdx::expected::value_type, std::error_code> step_( + size_t sz) { + auto buf = buffer_ + consumed_; + + if (sz != std::numeric_limits::max()) { + // not enough data. + if (buf.size() < sz) { + return stdx::unexpected(make_error_code(codec_errc::not_enough_input)); + } + } + + auto decode_res = Codec::decode(net::buffer(buf, sz), caps_); + if (!decode_res) return stdx::unexpected(decode_res.error()); + + consumed_ += decode_res->first; + return decode_res->second; + } + + net::const_buffer buffer_; + const capabilities::value_type caps_; + size_t consumed_; + + result_type res_; +}; + +/** + * accumulator of encoded buffers. + * + * writes the .step()ed encoded types into buffer. + * + * EncodeBufferAccumulator(buffer, caps) + * .step(wire::VarInt(42)) + * .step(wire::VarInt(512)) + * .result() + * + * The class should be used together with EncodeSizeAccumulator which shares + * the same interface. + */ +class EncodeBufferAccumulator { + public: + using result_type = stdx::expected; + + /** + * construct a encode-buffer-accumulator. + * + * @param buffer mutable-buffer to encode into + * @param caps protocol capabilities + * @param consumed bytes already used in the in buffer + */ + EncodeBufferAccumulator(net::mutable_buffer buffer, + capabilities::value_type caps, size_t consumed = 0) + : buffer_{buffer}, caps_{caps}, consumed_{consumed} {} + + /** + * encode a T into the buffer and move position forward. + * + * no-op of a previous step failed. + */ + template + EncodeBufferAccumulator &step(const T &v) { + if (!res_) return *this; + + auto res = Codec(v, caps_).encode(buffer_ + consumed_); + if (!res) { // it failed. + res_ = res; + } else { + consumed_ += *res; + } + + return *this; + } + + /** + * get result the steps(). + * + * @returns last used position in buffer, or first error in case of a step() + * failed. + */ + result_type result() const { + if (!res_) return res_; + + return {consumed_}; + } + + private: + const net::mutable_buffer buffer_; + const capabilities::value_type caps_; + size_t consumed_{}; + + result_type res_; +}; + +/** + * accumulates the sizes of encoded T's. + * + * e.g. the size of tw + * + * EncodeSizeAccumulator(caps) + * .step(wire::VarInt(42)) // 1 + * .step(wire::VarInt(512)) // 2 + * .result() // = 3 + * + * The class should be used together with EncodeBufferAccumulator which shares + * the same interface. + */ +class EncodeSizeAccumulator { + public: + using result_type = size_t; + + /** + * construct a EncodeSizeAccumulator. + */ + constexpr explicit EncodeSizeAccumulator(capabilities::value_type caps) + : caps_{caps} {} + + /** + * accumulate the size() of encoded T. + * + * calls Codec(v, caps).size() + */ + template + constexpr EncodeSizeAccumulator &step(const T &v) noexcept { + consumed_ += Codec(v, caps_).size(); + + return *this; + } + + /** + * @returns size of all steps(). + */ + constexpr result_type result() const { return consumed_; } + + private: + size_t consumed_{}; + const capabilities::value_type caps_; +}; + +/** + * CRTP base for the Codec's encode part. + * + * derived classes must provide a 'accumulate_fields()' which + * maps each field by the Mapper and returns the result + * + * used by .size() and .encode() as both have to process the same + * fields in the same order, just with different mappers + */ +template +class EncodeBase { + public: + constexpr explicit EncodeBase(capabilities::value_type caps) : caps_{caps} {} + + constexpr size_t size() const noexcept { + return static_cast(this)->accumulate_fields( + EncodeSizeAccumulator(caps_)); + } + + stdx::expected encode( + net::mutable_buffer buffer) const { + return static_cast(this)->accumulate_fields( + EncodeBufferAccumulator(buffer, caps_)); + } + + constexpr capabilities::value_type caps() const noexcept { return caps_; } + + private: + const capabilities::value_type caps_; +}; + +} // namespace impl +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_binary.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_binary.h new file mode 100644 index 0000000..435f1e7 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_binary.h @@ -0,0 +1,713 @@ +/* + Copyright (c) 2023, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_BINARY_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_BINARY_H_ + +#include // size_t +#include // uint8_t +#include // memcpy +#include // error_code +#include // move + +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_binary.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { + +namespace impl { +template +struct BinaryTypeBase; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Decimal; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Tiny; + static constexpr const uint8_t byte_size = 1; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Short; + static constexpr const uint8_t byte_size = 2; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Long; + static constexpr const uint8_t byte_size = 4; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Float; + static constexpr const uint8_t byte_size = 4; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Double; + static constexpr const uint8_t byte_size = 8; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Null; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Timestamp; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::LongLong; + static constexpr const uint8_t byte_size = 8; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Int24; + static constexpr const uint8_t byte_size = 4; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Date; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Time; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::DateTime; +}; + +template <> +struct BinaryTypeBase { + static constexpr const uint16_t binary_field_type = field_type::Year; + static constexpr const uint8_t byte_size = 2; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Varchar; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Bit; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Json; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::NewDecimal; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Enum; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Set; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::TinyBlob; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::MediumBlob; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::LongBlob; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Blob; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::String; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::VarString; +}; + +template +struct BinaryTypeBase> { + static constexpr const uint16_t binary_field_type = field_type::Geometry; +}; + +// codec base + +template +class FixedIntCodec : public impl::EncodeBase> { + static constexpr const int byte_size = BinaryTypeBase::byte_size; + + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + accu.step(wire::FixedInt(val_.value())); + + return accu.result(); + } + + public: + using value_type = T; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr FixedIntCodec(value_type val, capabilities::value_type caps) + : base_(caps), val_{std::move(val)} {} + + static constexpr uint16_t type() { + return BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto value_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(value_res->value())); + } + + private: + const value_type val_; +}; + +template +class FloatCodec : public impl::EncodeBase> { + static constexpr const int byte_size = BinaryTypeBase::byte_size; + + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + // reinterpret_cast<> isn't allowed in constexpr. + // + // Use a union as a workaround. + union overlapped_storage { + constexpr overlapped_storage(typename value_type::value_type val) + : val_(val) {} + + typename value_type::value_type val_; + + char addr_[sizeof(val_)]; + } overlapped(val_.value()); + + static_assert(sizeof(overlapped.val_) == byte_size); + + return accu + .step(borrowed::wire::String{ + std::string_view((&overlapped.addr_[0]), sizeof(overlapped.val_))}) + .result(); + } + + public: + using value_type = T; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr FloatCodec(value_type val, capabilities::value_type caps) + : base_(caps), val_{std::move(val)} {} + + static constexpr uint16_t type() { + return BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto value_res = accu.template step(byte_size); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + typename T::value_type val; + + memcpy(&val, value_res->value().data(), byte_size); + + return std::make_pair(accu.result().value(), value_type(val)); + } + + private: + const value_type val_; +}; + +template +class StringCodec : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + accu.step(borrowable::wire::String(val_.value())); + + return accu.result(); + } + + public: + using value_type = T; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr StringCodec(value_type val, capabilities::value_type caps) + : base_(caps), val_{std::move(val)} {} + + static constexpr uint16_t type() { + return BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto value_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(value_res->value())); + } + + private: + const value_type val_; +}; + +template +class DatetimeCodec : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + const bool has_ms = val_.microsecond(); + const bool has_time = + val_.hour() || val_.minute() || val_.second() || has_ms; + const bool has_date = val_.year() || val_.month() || val_.day() || has_time; + + if (has_date) { + accu.step(wire::FixedInt<2>(val_.year())) + .step(wire::FixedInt<1>(val_.month())) + .step(wire::FixedInt<1>(val_.day())); + if (has_time) { + accu.step(wire::FixedInt<1>(val_.hour())) + .step(wire::FixedInt<1>(val_.minute())) + .step(wire::FixedInt<1>(val_.second())); + if (has_ms) { + accu.step(wire::FixedInt<4>(val_.microsecond())); + } + } + } + return accu.result(); + } + + public: + using value_type = T; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr DatetimeCodec(value_type val, capabilities::value_type caps) + : base_(caps), val_{std::move(val)} {} + + static constexpr uint16_t type() { + return BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto year_res = accu.template try_step>(); + if (!year_res) { + return std::make_pair(accu.result().value(), value_type()); + } + + auto month_res = accu.template step>(); + auto day_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto hour_res = accu.template try_step>(); + if (!hour_res) { + return std::make_pair( + accu.result().value(), + value_type(year_res->value(), month_res->value(), day_res->value())); + } + + auto minute_res = accu.template step>(); + auto second_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto microsecond_res = accu.template try_step>(); + if (!microsecond_res) { + return std::make_pair( + accu.result().value(), + value_type(year_res->value(), month_res->value(), day_res->value(), + hour_res->value(), minute_res->value(), + second_res->value())); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(year_res->value(), month_res->value(), day_res->value(), + hour_res->value(), minute_res->value(), second_res->value(), + microsecond_res->value())); + } + + private: + value_type val_; +}; + +template +class TimeCodec : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + if (val_.days() || val_.hour() || val_.minute() || val_.second() || + val_.microsecond()) { + accu.step(wire::FixedInt<1>(val_.is_negative())) + .step(wire::FixedInt<4>(val_.days())) + .step(wire::FixedInt<1>(val_.hour())) + .step(wire::FixedInt<1>(val_.minute())) + .step(wire::FixedInt<1>(val_.second())); + if (val_.microsecond()) { + accu.step(wire::FixedInt<4>(val_.microsecond())); + } + } + return accu.result(); + } + + public: + using value_type = T; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr TimeCodec(value_type val, capabilities::value_type caps) + : base_(caps), val_{std::move(val)} {} + + static constexpr uint16_t type() { + return BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto is_negative_res = accu.template try_step>(); + if (!is_negative_res) { + return std::make_pair(accu.result().value(), value_type()); + } + auto days_res = accu.template step>(); + auto hour_res = accu.template step>(); + auto minute_res = accu.template step>(); + auto second_res = accu.template step>(); + auto microsecond_res = accu.template try_step>(); + + if (!microsecond_res) { + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(is_negative_res->value(), days_res->value(), + hour_res->value(), minute_res->value(), + second_res->value())); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(is_negative_res->value(), days_res->value(), + hour_res->value(), minute_res->value(), second_res->value(), + microsecond_res->value())); + } + + private: + const value_type val_; +}; + +} // namespace impl + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.result(); + } + + public: + using value_type = borrowable::binary::Null; + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr Codec(value_type /* val */, capabilities::value_type caps) + : base_(caps) {} + + static constexpr uint16_t type() { + return impl::BinaryTypeBase::binary_field_type; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer & /* buffer */, + capabilities::value_type /* caps */) { + return std::make_pair(0, value_type()); + } +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FixedIntCodec { + public: + using FixedIntCodec::FixedIntCodec; +}; + +template <> +class Codec + : public impl::FloatCodec { + public: + using FloatCodec::FloatCodec; +}; + +template <> +class Codec + : public impl::FloatCodec { + public: + using FloatCodec::FloatCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec< + Borrowed, borrowable::binary::MediumBlob>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec< + Borrowed, borrowable::binary::NewDecimal>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template +class Codec> + : public impl::StringCodec> { + public: + using impl::StringCodec>::StringCodec; +}; + +template <> +class Codec + : public impl::DatetimeCodec { + public: + using DatetimeCodec::DatetimeCodec; +}; + +template <> +class Codec + : public impl::DatetimeCodec { + public: + using DatetimeCodec::DatetimeCodec; +}; + +template <> +class Codec + : public impl::DatetimeCodec { + public: + using DatetimeCodec::DatetimeCodec; +}; + +template <> +class Codec + : public impl::TimeCodec { + public: + using TimeCodec::TimeCodec; +}; + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_clone.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_clone.h new file mode 100644 index 0000000..cd3af89 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_clone.h @@ -0,0 +1,393 @@ +/* + Copyright (c) 2021, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_CLONE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_CLONE_H_ + +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_clone.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" + +namespace classic_protocol { +namespace clone::client { +enum class CommandByte { + Init = 0x01, + Attach, + Reinit, + Execute, + Ack, + Exit, +}; +} // namespace clone::client + +/** + * codec for clone::client::Init message. + * + * - Fixed<1> cmd_byte + * - Fixed<4> protocol version + * - Fixed<4> ddl_timeout + * - 0-or-more + * - 1 SE type + * - Fixed<4> locator_len + * - String locator + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())) + .step(wire::FixedInt<4>(v_.protocol_version)) + .step(wire::FixedInt<4>(v_.ddl_timeout)) + .result(); + } + + public: + using value_type = clone::client::Init; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Init); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + auto protocol_version_res = accu.template step>(); + if (!protocol_version_res) { + return stdx::unexpected(protocol_version_res.error()); + } + auto ddl_timeout_res = accu.template step>(); + if (!ddl_timeout_res) { + return stdx::unexpected(ddl_timeout_res.error()); + } + + // TODO(jkneschk): if there is more data, 1-or-more Locators + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +// Reinit decodes the same way as Init +// Ack decodes the same way as Init + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::client::Execute; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Execute); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::client::Attach; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Attach); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::client::Reinit; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Reinit); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::client::Ack; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Ack); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::client::Exit; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::client::CommandByte::Exit); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +namespace clone::server { +enum class CommandByte { + Locators = 0x01, + DataDescriptor, + Data, + Plugin, + Config, + Collation, + PluginV2, // version: 0x0101 + ConfigV3, // version: 0x0102 + Complete = 99, + Error = 100, +}; +} + +// clone::string: +// Fixed<4> len +// String payload +// +// Plugin: +// - clone::string +// PluginV2: +// - key clone::string +// - value clone::string +// Collation: +// - clone::string +// Config +// - key clone::string +// - value clone::string + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::server::Complete; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::server::CommandByte::Complete); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<1>(cmd_byte())).result(); + } + + public: + using value_type = clone::server::Error; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(clone::server::CommandByte::Error); + } + + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + + return std::make_pair(accu.result().value(), value_type()); + } + + private: + const value_type v_; +}; + +} // namespace classic_protocol +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_error.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_error.h new file mode 100644 index 0000000..c5c7469 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_error.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_ERROR_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_ERROR_H_ + +// error-domain for classic_protocol::codec errors + +#include // error_code + +namespace classic_protocol { + +enum class codec_errc { + // precondition failed like "first byte == cmd_byte()" + invalid_input = 1, + // not enough input to satisfy the length requirements like "FixedInt<1>" + not_enough_input, + // no nul-terminator found in input + missing_nul_term, + // capability not supported for this message + capability_not_supported, + // statement-id not found + statement_id_not_found, + // field-type unknown + field_type_unknown +}; +} // namespace classic_protocol + +namespace std { +template <> +struct is_error_code_enum + : public std::true_type {}; +} // namespace std + +namespace classic_protocol { +inline const std::error_category &codec_category() noexcept { + class error_category_impl : public std::error_category { + public: + const char *name() const noexcept override { return "codec"; } + std::string message(int ev) const override { + switch (static_cast(ev)) { + case codec_errc::invalid_input: + return "invalid input"; + case codec_errc::not_enough_input: + return "input too short"; + case codec_errc::missing_nul_term: + return "missing nul-terminator"; + case codec_errc::capability_not_supported: + return "capability not supported"; + case codec_errc::statement_id_not_found: + return "statement-id not found"; + case codec_errc::field_type_unknown: + return "unknown field-type"; + default: + return "unknown"; + } + } + }; + + static error_category_impl instance; + return instance; +} + +inline std::error_code make_error_code(codec_errc e) noexcept { + return {static_cast(e), codec_category()}; +} + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_frame.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_frame.h new file mode 100644 index 0000000..bb36908 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_frame.h @@ -0,0 +1,189 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_FRAME_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_FRAME_H_ + +#include // size_t +#include // uint8_t +#include +#include // error_code +#include // move + +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" +#include "mysqlrouter/classic_protocol_frame.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { + +/** + * Codec of a Frame Header. + */ +template <> +class Codec : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<3>(v_.payload_size())) + .step(wire::FixedInt<1>(v_.seq_id())) + .result(); + } + + public: + using value_type = frame::Header; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr size_t max_size() noexcept { return 4; } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto payload_size_res = accu.template step>(); + auto seq_id_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(payload_size_res->value(), seq_id_res->value())); + } + + private: + const value_type v_; +}; + +/** + * Codec of Compressed Header. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + auto accumulate_fields(Accumulator &&accu) const { + return accu.step(wire::FixedInt<3>(v_.payload_size())) + .step(wire::FixedInt<1>(v_.seq_id())) + .step(wire::FixedInt<3>(v_.uncompressed_size())) + .result(); + } + + public: + using value_type = frame::CompressedHeader; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static size_t max_size() noexcept { return 7; } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto payload_size_res = accu.template step>(); + auto seq_id_res = accu.template step>(); + auto uncompressed_size_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(payload_size_res->value(), seq_id_res->value(), + uncompressed_size_res->value())); + } + + private: + const value_type v_; +}; + +/** + * Codec for a Frame. + * + * Frame is + * + * - header + * - payload + */ +template +class Codec> + : public impl::EncodeBase>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu + .step(frame::Header( + Codec(v_.payload(), this->caps()).size(), v_.seq_id())) + .step(PayloadType(v_.payload())) + .result(); + } + + public: + using value_type = frame::Frame; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static stdx::expected, std::error_code> decode( + net::const_buffer buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + auto header_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + constexpr const size_t header_size{Codec::max_size()}; + + // check the payload is at least what we expect. + if (buffer.size() < header_size + header_res->payload_size()) { + return stdx::unexpected( + make_error_code(classic_protocol::codec_errc::not_enough_input)); + } + + auto payload_res = + accu.template step(header_res->payload_size()); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(header_res->seq_id(), payload_res.value())); + } + + private: + const value_type v_; +}; +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_message.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_message.h new file mode 100644 index 0000000..27789bd --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_message.h @@ -0,0 +1,3787 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_MESSAGE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_MESSAGE_H_ + +#include +#include // size_t +#include // uint8_t +#include // error_code +#include // move + +#include "mysql/harness/stdx/expected.h" +#include "mysql/harness/stdx/ranges.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_binary.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" +#include "mysqlrouter/classic_protocol_constants.h" +#include "mysqlrouter/classic_protocol_message.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { + +/** + * codec for server Greeting message. + * + * 3.21 (protocol_version 9) + * + * FixedInt<1> protocol_version [0x09] + * NulTermString server_version + * FixedInt<4> connection_id + * NulTermString auth-method-data + * + * 3.21 and later (protocol_version 10) + * + * FixedInt<1> protocol_version [0x0a] + * NulTermString server_version + * FixedInt<4> connection_id + * NulTermString auth-method-data + * FixedInt<2> capabilities (lower 16bit) + * + * 3.23 and later add: + * + * FixedInt<1> collation + * FixedInt<2> status flags + * FixedInt<2> capabilities (upper 16bit) + * FixedInt<1> length of auth-method-data or 0x00 + * String<10> reserved + * + * if capabilities.secure_connection is set, adds + * + * String auth-method-data-2 + * + * if capabilities.plugin_auth is set, adds + * + * NulTermString auth-method + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + if (v_.protocol_version() == 0x09) { + return accu.step(bw::FixedInt<1>(v_.protocol_version())) + .step(bw::NulTermString(v_.version())) + .step(bw::FixedInt<4>(v_.connection_id())) + .step(bw::NulTermString(v_.auth_method_data().substr(0, 8))) + .result(); + } else { + uint8_t auth_method_data_size{0}; + if (v_.capabilities()[classic_protocol::capabilities::pos::plugin_auth]) { + auth_method_data_size = v_.auth_method_data().size(); + } + + accu.step(bw::FixedInt<1>(v_.protocol_version())) + .step(bw::NulTermString(v_.version())) + .step(bw::FixedInt<4>(v_.connection_id())) + .step(bw::NulTermString(v_.auth_method_data().substr(0, 8))) + .step(bw::FixedInt<2>(v_.capabilities().to_ulong() & 0xffff)); + + if ((v_.capabilities().to_ullong() >= (1 << 16)) || + v_.status_flags().any() || (v_.collation() != 0)) { + accu.step(bw::FixedInt<1>(v_.collation())) + .step(bw::FixedInt<2>(v_.status_flags().to_ulong())) + .step( + bw::FixedInt<2>((v_.capabilities().to_ulong() >> 16) & 0xffff)) + .step(bw::FixedInt<1>(auth_method_data_size)) + .step(bw::String(std::string(10, '\0'))); + if (v_.capabilities() + [classic_protocol::capabilities::pos::secure_connection]) { + accu.step(bw::String(v_.auth_method_data().substr(8))); + if (v_.capabilities() + [classic_protocol::capabilities::pos::plugin_auth]) { + accu.step(bw::NulTermString(v_.auth_method_name())); + } + } + } + return accu.result(); + } + } + + public: + using value_type = borrowable::message::server::Greeting; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + // proto-version + auto protocol_version_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (protocol_version_res->value() != 0x09 && + protocol_version_res->value() != 0x0a) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto version_res = accu.template step>(); + auto connection_id_res = accu.template step>(); + auto auth_method_data_res = + accu.template step>(); + + if (protocol_version_res->value() == 0x09) { + return std::make_pair( + accu.result().value(), + value_type(protocol_version_res->value(), version_res->value(), + connection_id_res->value(), auth_method_data_res->value(), + 0, 0, 0, {})); + } else { + // capabilities are split into two a lower-2-byte part and a + // higher-2-byte + auto cap_lower_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // 3.21.x doesn't send more. + if (buffer_size(buffer) <= accu.result().value()) { + return std::make_pair( + accu.result().value(), + value_type(protocol_version_res->value(), version_res->value(), + connection_id_res->value(), + auth_method_data_res->value(), cap_lower_res->value(), + 0x0, 0x0, {})); + } + + // if there's more data + auto collation_res = accu.template step>(); + auto status_flags_res = accu.template step>(); + auto cap_hi_res = accu.template step>(); + + // before we use cap_hi|cap_low check they don't have an error + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + classic_protocol::capabilities::value_type capabilities( + cap_lower_res->value() | (cap_hi_res->value() << 16)); + + size_t auth_method_data_len{13}; + if (capabilities[classic_protocol::capabilities::pos::plugin_auth]) { + auto auth_method_data_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // should be 21, but least 8 + if (auth_method_data_len_res->value() < 8) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auth_method_data_len = auth_method_data_len_res->value() - 8; + } else { + accu.template step(1); // should be 0 ... + } + + accu.template step(10); // skip the filler + + stdx::expected, std::error_code> + auth_method_data_2_res; + stdx::expected, std::error_code> + auth_method_res; + if (capabilities + [classic_protocol::capabilities::pos::secure_connection]) { + // auth-method-data + auth_method_data_2_res = + accu.template step>(auth_method_data_len); + + if (capabilities[classic_protocol::capabilities::pos::plugin_auth]) { + // auth_method + auth_method_res = accu.template step>(); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type( + protocol_version_res->value(), version_res->value(), + connection_id_res->value(), + auth_method_data_res->value() + auth_method_data_2_res->value(), + capabilities, collation_res->value(), status_flags_res->value(), + auth_method_res->value())); + } + } + + private: + const value_type v_; +}; + +/** + * codec for server::AuthMethodSwitch message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())); + + if (this->caps()[classic_protocol::capabilities::pos::plugin_auth]) { + accu.step(bw::NulTermString(v_.auth_method())) + .step(bw::String(v_.auth_method_data())); + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::AuthMethodSwitch; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() noexcept { return 0xfe; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::plugin_auth; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + // proto-version + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + if (!caps[classic_protocol::capabilities::pos::plugin_auth]) { + return std::make_pair(accu.result().value(), value_type()); + } + + auto auth_method_res = accu.template step>(); + auto auth_method_data_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(auth_method_res->value(), auth_method_data_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for server::AuthMethodData message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::String(v_.auth_method_data())) + .result(); + } + + public: + using value_type = borrowable::message::server::AuthMethodData; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() noexcept { return 0x01; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auto auth_method_data_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(auth_method_data_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for server-side Ok message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::VarInt(v_.affected_rows())) + .step(bw::VarInt(v_.last_insert_id())); + + if (this->caps()[capabilities::pos::protocol_41] || + this->caps()[capabilities::pos::transactions]) { + accu.step(bw::FixedInt<2>(v_.status_flags().to_ulong())); + if (this->caps().test(capabilities::pos::protocol_41)) { + accu.step(bw::FixedInt<2>(v_.warning_count())); + } + } + + // if there is only empty "message" or "session-data", skip the rest. + const bool has_session_track_cap = + this->caps().test(capabilities::pos::session_track); + + if (!v_.message().empty() || has_session_track_cap) { + accu.step(bw::VarString(v_.message())); + if (has_session_track_cap && + v_.status_flags().test(status::pos::session_state_changed)) { + accu.step(bw::VarString(v_.session_changes())); + } + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::Ok; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() noexcept { return 0x00; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::session_track | capabilities::transactions | + capabilities::protocol_41; + } + + /** + * decode a server::Ok message from a buffer-sequence. + * + * precondition: + * - input starts with cmd_byte() + * + * @param buffer input buffser sequence + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::invalid_input if preconditions aren't met + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto affected_rows_res = accu.template step(); + auto last_insert_id_res = accu.template step(); + + stdx::expected, std::error_code> status_flags_res(0); + stdx::expected, std::error_code> warning_count_res(0); + if (caps[capabilities::pos::protocol_41] || + caps[capabilities::pos::transactions]) { + status_flags_res = accu.template step>(); + if (caps[capabilities::pos::protocol_41]) { + warning_count_res = accu.template step>(); + } + } + + stdx::expected, std::error_code> + session_changes_res; + // if there is more data. + auto message_res = accu.template try_step>(); + if (message_res) { + if (caps[capabilities::pos::session_track]) { + if (status_flags_res->value() & + status::session_state_changed.to_ulong()) { + session_changes_res = accu.template step>(); + } + } + } else { + // no message field == empty message. + message_res = {}; + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(affected_rows_res->value(), last_insert_id_res->value(), + status_flags_res->value(), warning_count_res->value(), + message_res->value(), session_changes_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for server-side Eof message. + * + * Eof message is encoded differently dependending on protocol capabiltiies, + * but always starts with: + * + * - 0xef + * + * If capabilities has text_result_with_session_tracking, it is followed by + * - [rest of Ok packet] + * + * otherwise, if capabilities has protocol_41 + * - FixedInt<2> warning-count + * - FixedInt<2> status flags + * + * otherwise + * - nothing + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())); + + auto shared_caps = this->caps(); + + if (shared_caps.test( + capabilities::pos::text_result_with_session_tracking)) { + accu.step(bw::VarInt(v_.affected_rows())) + .step(bw::VarInt(v_.last_insert_id())); + + if (shared_caps[capabilities::pos::protocol_41] || + shared_caps[capabilities::pos::transactions]) { + accu.step(bw::FixedInt<2>(v_.status_flags().to_ulong())); + if (shared_caps[capabilities::pos::protocol_41]) { + accu.step(bw::FixedInt<2>(v_.warning_count())); + } + } + + if (shared_caps[capabilities::pos::session_track]) { + if (!v_.message().empty() || + v_.status_flags()[status::pos::session_state_changed]) { + // only write message and session-changes if both of them aren't + // empty. + accu.step(bw::VarString(v_.message())); + if (v_.status_flags()[status::pos::session_state_changed]) { + accu.step(bw::VarString(v_.session_changes())); + } + } + } else { + accu.step(bw::String(v_.message())); + } + } else if (shared_caps[capabilities::pos::protocol_41]) { + accu.step(bw::FixedInt<2>(v_.warning_count())) + .step(bw::FixedInt<2>(v_.status_flags().to_ulong())); + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::Eof; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() noexcept { return 0xfe; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::text_result_with_session_tracking | + capabilities::session_track | capabilities::transactions | + capabilities::protocol_41; + } + + /** + * decode a server::Eof message from a buffer-sequence. + * + * capabilities checked: + * - protocol_41 + * - text_resultset_with_session_tracking + * + * precondition: + * - input starts with cmd_byte() + * + * @param buffer input buffser sequence + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::invalid_input if preconditions aren't met + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + const auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + if (caps[capabilities::pos::text_result_with_session_tracking]) { + const auto affected_rows_res = accu.template step(); + const auto last_insert_id_res = accu.template step(); + + stdx::expected, std::error_code> status_flags_res(0); + stdx::expected, std::error_code> warning_count_res(0); + if (caps[capabilities::pos::protocol_41] || + caps[capabilities::pos::transactions]) { + status_flags_res = accu.template step>(); + if (caps[capabilities::pos::protocol_41]) { + warning_count_res = accu.template step>(); + } + } + + stdx::expected, std::error_code> message_res; + stdx::expected, std::error_code> + session_state_info_res; + if (caps[capabilities::pos::session_track]) { + // when session-track is supported, the 'message' part is a VarString. + // But only if there is actually session-data and the message has data. + const auto var_message_res = + accu.template try_step>(); + if (var_message_res) { + // set the message from the var-string + message_res = var_message_res.value(); + } + + if (status_flags_res->value() & + status::session_state_changed.to_ulong()) { + session_state_info_res = + accu.template step>(); + } + } else { + message_res = accu.template step>(); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(affected_rows_res->value(), last_insert_id_res->value(), + status_flags_res->value(), warning_count_res->value(), + message_res->value(), session_state_info_res->value())); + } else if (caps[capabilities::pos::protocol_41]) { + const auto warning_count_res = accu.template step>(); + const auto status_flags_res = accu.template step>(); + + return std::make_pair( + accu.result().value(), + value_type(status_flags_res->value(), warning_count_res->value())); + } else { + return std::make_pair(accu.result().value(), value_type()); + } + } + + private: + const value_type v_; +}; + +/** + * codec for Error message. + * + * note: Format overview: + * + * 3.21: protocol_version <= 9 [not supported] + * + * FixedInt<1> 0xff + * String message + * + * 3.21: protocol_version > 9 + * + * FixedInt<1> 0xff + * FixedInt<2> error_code + * String message + * + * 4.1 and later: + * + * FixedInt<1> 0xff + * FixedInt<2> error_code + * '#' + * String<5> sql_state + * String message + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<2>(v_.error_code())); + if (this->caps()[capabilities::pos::protocol_41]) { + accu.step(bw::FixedInt<1>('#')) + .step(bw::String(v_.sql_state())); + } + + return accu.step(bw::String(v_.message())).result(); + } + + public: + using value_type = borrowable::message::server::Error; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() { return 0xff; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::protocol_41; + } + + static constexpr size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + // decode all fields, check result later before they are used. + auto error_code_res = accu.template step>(); + stdx::expected, std::error_code> sql_state_res; + if (caps[capabilities::pos::protocol_41]) { + auto sql_state_hash_res = accu.template step>(); + if (!sql_state_hash_res) { + return stdx::unexpected(sql_state_hash_res.error()); + } + sql_state_res = accu.template step>(5); + } + auto message_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(error_code_res->value(), message_res->value(), + sql_state_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for ColumnCount message. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::VarInt(v_.count())).result(); + } + + public: + using value_type = borrowable::message::server::ColumnCount; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto count_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(count_res->value())); + } + + private: + const value_type v_; +}; + +/** + * Codec of ColumnMeta. + * + * capabilities checked: + * - protocol_41 + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + if (!this->caps()[capabilities::pos::protocol_41]) { + accu.step(bw::VarString(v_.table())) + .step(bw::VarString(v_.name())) + .step(bw::VarInt(3)) + .step(bw::FixedInt<3>(v_.column_length())) + .step(bw::VarInt(1)) + .step(bw::FixedInt<1>(v_.type())); + + if (this->caps()[capabilities::pos::long_flag]) { + accu.step(bw::VarInt(3)) + .step(bw::FixedInt<2>(v_.flags().to_ulong())) + .step(bw::FixedInt<1>(v_.decimals())); + } else { + accu.step(bw::VarInt(2)) + .step(bw::FixedInt<1>(v_.flags().to_ulong())) + .step(bw::FixedInt<1>(v_.decimals())); + } + + return accu.result(); + } else { + return accu.step(bw::VarString(v_.catalog())) + .step(bw::VarString(v_.schema())) + .step(bw::VarString(v_.table())) + .step(bw::VarString(v_.orig_table())) + .step(bw::VarString(v_.name())) + .step(bw::VarString(v_.orig_name())) + .step(bw::VarInt(12)) + .step(bw::FixedInt<2>(v_.collation())) + .step(bw::FixedInt<4>(v_.column_length())) + .step(bw::FixedInt<1>(v_.type())) + .step(bw::FixedInt<2>(v_.flags().to_ulong())) + .step(bw::FixedInt<1>(v_.decimals())) + .step(bw::FixedInt<2>(0)) + .result(); + } + } + + public: + using value_type = borrowable::message::server::ColumnMeta; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + if (!caps[capabilities::pos::protocol_41]) { + // 3.2x protocol used up to 4.0.x + + // bit-size of the 'flags' field + const uint8_t flags_size = caps[capabilities::pos::long_flag] ? 2 : 1; + + const auto table_res = accu.template step>(); + const auto name_res = accu.template step>(); + + const auto column_length_len_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (column_length_len_res->value() != 3) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + const auto column_length_res = accu.template step>(); + const auto type_len_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (type_len_res->value() != 1) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + const auto type_res = accu.template step>(); + const auto flags_and_decimals_len_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (flags_and_decimals_len_res->value() != flags_size + 1) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + stdx::expected, std::error_code> flags_and_decimals_res( + 0); + if (flags_size == 2) { + flags_and_decimals_res = accu.template step>(); + } else { + const auto small_flags_and_decimals_res = + accu.template step>(); + if (small_flags_and_decimals_res) { + flags_and_decimals_res = + bw::FixedInt<3>(small_flags_and_decimals_res->value()); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + const uint16_t flags = + flags_and_decimals_res->value() & ((1 << (flags_size * 8)) - 1); + const uint8_t decimals = + flags_and_decimals_res->value() >> (flags_size * 8); + + return std::make_pair( + accu.result().value(), + value_type({}, {}, table_res->value(), {}, name_res->value(), {}, {}, + column_length_res->value(), type_res->value(), flags, + decimals)); + } else { + const auto catalog_res = accu.template step>(); + const auto schema_res = accu.template step>(); + const auto table_res = accu.template step>(); + const auto orig_table_res = accu.template step>(); + const auto name_res = accu.template step>(); + const auto orig_name_res = accu.template step>(); + + /* next is a collection of fields which is wrapped inside a varstring of + * 12-bytes size */ + const auto other_len_res = accu.template step(); + + if (other_len_res->value() != 12) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + const auto collation_res = accu.template step>(); + const auto column_length_res = accu.template step>(); + const auto type_res = accu.template step>(); + const auto flags_res = accu.template step>(); + const auto decimals_res = accu.template step>(); + + accu.template step(2); // fillers + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(catalog_res->value(), schema_res->value(), + table_res->value(), orig_table_res->value(), + name_res->value(), orig_name_res->value(), + collation_res->value(), column_length_res->value(), + type_res->value(), flags_res->value(), + decimals_res->value())); + } + } + + private: + const value_type v_; +}; + +/** + * codec for server's SendFileRequest response. + * + * sent as response after client::Query + * + * layout: + * + * 0xfb + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::String(v_.filename())) + .result(); + } + + public: + using value_type = borrowable::message::server::SendFileRequest; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static constexpr uint8_t cmd_byte() noexcept { return 0xfb; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto filename_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(filename_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for server::StmtPrepareOk message. + * + * format: + * + * - FixedInt<1> == 0x00 [ok] + * - FixedInt<4> stmt-id + * - FixedInt<2> column-count + * - FixedInt<2> param-count + * - FixedInt<1> == 0x00 [filler] + * - FixedInt<2> warning-count + * + * If caps contains optional_resultset_metadata: + * + * - FixedInt<1> with_metadata + * + * sent as response after a client::StmtPrepare + */ +template <> +class Codec + : public impl::EncodeBase< + Codec> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .step(bw::FixedInt<2>(v_.column_count())) + .step(bw::FixedInt<2>(v_.param_count())) + .step(bw::FixedInt<1>(0)) + .step(bw::FixedInt<2>(v_.warning_count())); + + if (caps()[capabilities::pos::optional_resultset_metadata]) { + accu.step(bw::FixedInt<1>(v_.with_metadata())); + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::StmtPrepareOk; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { return 0x00; } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::optional_resultset_metadata; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!cmd_byte_res) return stdx::unexpected(cmd_byte_res.error()); + auto stmt_id_res = accu.template step>(); + auto column_count_res = accu.template step>(); + auto param_count_res = accu.template step>(); + auto filler_res = accu.template step>(); + if (!filler_res) return stdx::unexpected(filler_res.error()); + auto warning_count_res = accu.template step>(); + + // by default, metadata isn't optional + int8_t with_metadata{1}; + if (caps[capabilities::pos::optional_resultset_metadata]) { + auto with_metadata_res = accu.template step>(); + + if (with_metadata_res) { + with_metadata = with_metadata_res->value(); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(stmt_id_res->value(), column_count_res->value(), + param_count_res->value(), warning_count_res->value(), + with_metadata)); + } + + private: + const value_type v_; +}; + +/** + * codec for a Row from the server. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + for (const auto &field : v_) { + if (field) { + accu.step(bw::VarString(*field)); + } else { + accu.step(bw::Null()); + } + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::Row; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + std::vector fields; + + const size_t buf_size = buffer_size(buffer); + + while (accu.result() && (accu.result().value() < buf_size)) { + // field may other be a Null or a VarString + auto null_res = accu.template try_step(); + if (null_res) { + fields.emplace_back(std::nullopt); + } else { + auto field_res = accu.template step>(); + if (!field_res) return stdx::unexpected(field_res.error()); + + fields.emplace_back(field_res->value()); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), value_type(fields)); + } + + private: + const value_type v_; +}; + +/** + * codec for a StmtRow from the server. + * + * StmtRow is the Row of a StmtExecute's resultset. + * + * - 0x00 + * - NULL bitmap + * - non-NULL-values in binary encoding + * + * both encode and decode require type information to know: + * + * - size the NULL bitmap + * - length of each field + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(0)); + + // null-bitmap starts with a 2-bit offset + size_t bit_pos{2}; + uint8_t null_bit_byte{}; + + for (auto const &field : v_) { + if (!field) null_bit_byte |= (1 << bit_pos); + + if (++bit_pos > 7) { + accu.step(bw::FixedInt<1>(null_bit_byte)); + + bit_pos = 0; + null_bit_byte = 0; + } + } + + if (bit_pos != 0) accu.step(bw::FixedInt<1>(null_bit_byte)); + + for (auto [n, field] : stdx::views::enumerate(v_)) { + if (!field) continue; + + switch (v_.types()[n]) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::NewDecimal: + case field_type::Geometry: + accu.step(bw::VarInt(field->size())); + break; + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: + accu.step(bw::FixedInt<1>(field->size())); + break; + case field_type::LongLong: + case field_type::Double: + case field_type::Long: + case field_type::Int24: + case field_type::Float: + case field_type::Short: + case field_type::Year: + case field_type::Tiny: + // fixed size + break; + } + accu.step(borrowed::wire::String(*field)); + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::server::StmtRow; + using __base = impl::EncodeBase>; + + friend __base; + + Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps, + std::vector types) { + namespace bw = borrowable::wire; + + impl::DecodeBufferAccumulator accu(buffer, caps); + + const auto row_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // first byte is 0x00 + if (row_byte_res->value() != 0x00) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + const auto nullbits_res = + accu.template step>(bytes_per_bits(types.size())); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + const auto nullbits = nullbits_res->value(); + + std::vector values; + + for (size_t n{}, bit_pos{2}, byte_pos{}; n < types.size(); ++n, ++bit_pos) { + if (bit_pos > 7) { + bit_pos = 0; + ++byte_pos; + } + + if (!(nullbits[byte_pos] & (1 << bit_pos))) { + stdx::expected field_size_res( + stdx::unexpected(make_error_code(std::errc::invalid_argument))); + switch (types[n]) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::NewDecimal: + case field_type::Geometry: { + auto string_field_size_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + field_size_res = string_field_size_res->value(); + } break; + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: { + auto time_field_size_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + field_size_res = time_field_size_res->value(); + } break; + case field_type::LongLong: + case field_type::Double: + field_size_res = 8; + break; + case field_type::Long: + case field_type::Int24: + case field_type::Float: + field_size_res = 4; + break; + case field_type::Short: + case field_type::Year: + field_size_res = 2; + break; + case field_type::Tiny: + field_size_res = 1; + break; + } + + if (!field_size_res) { + return stdx::unexpected( + make_error_code(codec_errc::field_type_unknown)); + } + + const auto value_res = + accu.template step>(field_size_res.value()); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + values.push_back(value_res->value()); + } else { + values.emplace_back(std::nullopt); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), value_type(types, values)); + } + + private: + const value_type v_; +}; + +/** + * codec for server::Statistics message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::String(v_.stats())).result(); + } + + public: + using value_type = borrowable::message::server::Statistics; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto stats_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(stats_res->value())); + } + + private: + const value_type v_; +}; + +/** + * CRTP base for client-side commands that are encoded as a single byte. + */ +template +class CodecSimpleCommand + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(Base::cmd_byte())).result(); + } + + public: + using __base = impl::EncodeBase>; + + friend __base; + + constexpr CodecSimpleCommand(capabilities::value_type caps) : __base(caps) {} + + static constexpr size_t max_size() noexcept { return 1; } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != Base::cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + return std::make_pair(accu.result().value(), ValueType()); + } +}; + +enum class CommandByte { + Quit = 0x01, + InitSchema, + Query, + ListFields, + CreateDb, + DropDb, + Refresh, + Shutdown, + Statistics, + ProcessInfo, + Connect, + ProcessKill, + Debug, + Ping, + Time, + DelayedInsert, + ChangeUser, + BinlogDump, + TableDump, + ConnectOut, + RegisterReplica, + StmtPrepare, + StmtExecute, + StmtSendLongData, + StmtClose, + StmtReset, + SetOption, + StmtFetch, + Deamon, + BinlogDumpGtid, + ResetConnection, + Clone +}; + +/** + * codec for client's Quit command. + */ +template <> +class Codec + : public CodecSimpleCommand, + borrowable::message::client::Quit> { + public: + using value_type = borrowable::message::client::Quit; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Quit); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client's ResetConnection command. + */ +template <> +class Codec + : public CodecSimpleCommand< + Codec, + borrowable::message::client::ResetConnection> { + public: + using value_type = borrowable::message::client::ResetConnection; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::ResetConnection); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client's Ping command. + */ +template <> +class Codec + : public CodecSimpleCommand, + borrowable::message::client::Ping> { + public: + using value_type = borrowable::message::client::Ping; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Ping); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client's Statistics command. + */ +template <> +class Codec + : public CodecSimpleCommand, + borrowable::message::client::Statistics> { + public: + using value_type = borrowable::message::client::Statistics; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Statistics); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client's Debug command. + */ +template <> +class Codec + : public CodecSimpleCommand, + borrowable::message::client::Debug> { + public: + using value_type = borrowable::message::client::Debug; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Debug); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client's InitSchema command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::String(v_.schema())) + .result(); + } + + public: + using value_type = borrowable::message::client::InitSchema; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::InitSchema); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto schema_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(schema_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Query command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())); + + auto caps = this->caps(); + + if (caps.test(capabilities::pos::query_attributes)) { + uint64_t param_count = v_.values().size(); + accu.step(bw::VarInt(param_count)); // param_count + accu.step(bw::VarInt(1)); // param_set_count: always 1 + + if (param_count > 0) { + // mark all that are NULL in the nullbits + // + // - one bit per parameter to send + // - if a parameter is NULL, the bit is set, and later no value is + // added. + + uint8_t null_bit_byte{}; + int bit_pos{}; + + for (auto const ¶m : v_.values()) { + if (!param.value) null_bit_byte |= 1 << bit_pos; + + if (++bit_pos > 7) { + accu.step(bw::FixedInt<1>(null_bit_byte)); + + bit_pos = 0; + null_bit_byte = 0; + } + } + + if (bit_pos != 0) accu.step(bw::FixedInt<1>(null_bit_byte)); + + accu.step(bw::FixedInt<1>(1)); // new_param_bind_flag: always 1 + + for (const auto ¶m : v_.values()) { + accu.step(bw::FixedInt<2>(param.type_and_flags)) + .step(bw::VarString(param.name)); + } + + for (const auto ¶m : v_.values()) { + if (!param.value) continue; + + auto type = param.type_and_flags & 0xff; + switch (type) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::Json: + case field_type::NewDecimal: + case field_type::Geometry: + accu.template step(param.value->size()); + break; + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: + assert(param.value->size() <= 255); + + accu.template step>(param.value->size()); + + break; + case field_type::LongLong: + case field_type::Double: + assert(param.value->size() == 8); + + break; + case field_type::Long: + case field_type::Int24: + case field_type::Float: + assert(param.value->size() == 4); + + break; + case field_type::Short: + case field_type::Year: + assert(param.value->size() == 2); + + break; + case field_type::Tiny: + assert(param.value->size() == 1); + + break; + default: + assert(true || "unknown field-type"); + break; + } + accu.template step>(*param.value); + } + } + } + + accu.step(bw::String(v_.statement())); + return accu.result(); + } + + template + static stdx::expected decode_field_size( + Accu &accu, uint8_t type) { + namespace bw = borrowable::wire; + + switch (type) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::Json: + case field_type::NewDecimal: + case field_type::Geometry: { + auto string_field_size_res = accu.template step(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + return string_field_size_res->value(); + } + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: { + auto time_field_size_res = accu.template step>(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + return time_field_size_res->value(); + } + case field_type::LongLong: + case field_type::Double: + return 8; + case field_type::Long: + case field_type::Int24: + case field_type::Float: + return 4; + case field_type::Short: + case field_type::Year: + return 2; + case field_type::Tiny: + return 1; + } + + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + public: + using value_type = borrowable::message::client::Query; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Query); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::query_attributes; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + std::vector params; + if (caps.test(capabilities::pos::query_attributes)) { + // + auto param_count_res = accu.template step(); + if (!param_count_res) return stdx::unexpected(param_count_res.error()); + + // currently always 1. + auto param_set_count_res = accu.template step(); + if (!param_set_count_res) + return stdx::unexpected(param_set_count_res.error()); + + if (param_set_count_res->value() != 1) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + const auto param_count = param_count_res->value(); + if (param_count > 0) { + // bit-map + const auto nullbits_res = accu.template step>( + bytes_per_bits(param_count)); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + const auto nullbits = nullbits_res->value(); + + // always 1 + auto new_params_bound_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto new_params_bound = new_params_bound_res->value(); + if (new_params_bound != 1) { + // Always 1, malformed packet error of not 1 + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + // redundant, but protocol-docs says: + // + // 'if new-params-bind-flag == 1' + // + // therefore keep it. + if (new_params_bound == 1) { + for (long n{}; n < param_count; ++n) { + auto param_type_res = accu.template step>(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + auto param_name_res = accu.template step>(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + params.emplace_back(param_type_res->value(), + param_name_res->value(), + std::optional()); + } + } + + for (long n{}, nullbit_pos{}, nullbyte_pos{}; n < param_count; + ++n, ++nullbit_pos) { + if (nullbit_pos > 7) { + ++nullbyte_pos; + nullbit_pos = 0; + } + + if (!(nullbits[nullbyte_pos] & (1 << nullbit_pos))) { + auto ¶m = params[n]; + + auto field_size_res = + decode_field_size(accu, param.type_and_flags & 0xff); + if (!field_size_res) { + return stdx::unexpected(field_size_res.error()); + } + + auto param_value_res = accu.template step>( + field_size_res.value()); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + param.value = param_value_res->value(); + } + } + } + } + + auto statement_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(statement_res->value(), std::move(params))); + } + + private: + const value_type v_; +}; + +/** + * codec for client::SendFile message. + * + * sent by client as response to server::SendFileRequest + * + * format: + * + * - String payload + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::String(v_.payload())).result(); + } + + public: + using value_type = borrowable::message::client::SendFile; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto payload_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(payload_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's ListFields command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::NulTermString(v_.table_name())) + .step(bw::String(v_.wildcard())) + .result(); + } + + public: + using value_type = borrowable::message::client::ListFields; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::ListFields); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto table_name_res = accu.template step>(); + auto wildcard_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(table_name_res->value(), wildcard_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Reload command. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<1>(v_.cmds().to_ulong())) + .result(); + } + + public: + using value_type = borrowable::message::client::Reload; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Refresh); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto cmds_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), value_type(cmds_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Kill command. + * + * format: + * + * - FixedInt<1> == 0x0c, ProcessKill + * - FixedInt<4> id + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.connection_id())) + .result(); + } + + public: + using value_type = borrowable::message::client::Kill; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::ProcessKill); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto connection_id_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(connection_id_res->value())); + } + + private: + value_type v_; +}; + +/** + * codec for client's Prepared Statement command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::String(v_.statement())) + .result(); + } + + public: + using value_type = borrowable::message::client::StmtPrepare; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtPrepare); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(statement_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Execute Statement command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + auto caps = this->caps(); + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .step(bw::FixedInt<1>(v_.flags().to_ullong())) + .step(bw::FixedInt<4>(v_.iteration_count())); + + // num-params from the StmtPrepareOk + auto num_params = v_.values().size(); + + const bool supports_query_attributes = + caps.test(capabilities::pos::query_attributes); + + if (supports_query_attributes && + v_.flags().test(cursor::pos::param_count_available)) { + accu.step(bw::VarInt(num_params)); + } + + if (num_params == 0) return accu.result(); + + // mark all that are NULL in the nullbits + // + // - one bit per parameter to send + // - if a parameter is NULL, the bit is set, and later no value is added. + uint8_t null_bit_byte{}; + int bit_pos{}; + + for (auto const ¶m : v_.values()) { + if (!param.has_value()) null_bit_byte |= 1 << bit_pos; + + if (++bit_pos > 7) { + accu.step(bw::FixedInt<1>(null_bit_byte)); + + bit_pos = 0; + null_bit_byte = 0; + } + } + + if (bit_pos != 0) accu.step(bw::FixedInt<1>(null_bit_byte)); + + accu.step(bw::FixedInt<1>(v_.new_params_bound())); + + if (v_.new_params_bound()) { + for (const auto ¶m_def : v_.types()) { + accu.step(bw::FixedInt<2>(param_def.type_and_flags)); + + if (supports_query_attributes) { + accu.step(borrowed::wire::VarString(param_def.name)); + } + } + } + + for (auto [n, v] : stdx::views::enumerate(v_.values())) { + // add all the values that aren't NULL + if (!v.has_value()) continue; + + // write length of the type is a variable length + switch (v_.types()[n].type_and_flags & 0xff) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::NewDecimal: + case field_type::Geometry: + accu.step(bw::VarInt(v->size())); + break; + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: + accu.step(bw::FixedInt<1>(v->size())); + break; + case field_type::LongLong: + case field_type::Double: + case field_type::Long: + case field_type::Int24: + case field_type::Float: + case field_type::Short: + case field_type::Year: + case field_type::Tiny: + // fixed size + break; + default: + assert(false || "Unknown Type"); + } + accu.step(borrowed::wire::String(v.value())); + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::client::StmtExecute; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type val, capabilities::value_type caps) + : __base(caps), v_{std::move(val)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtExecute); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::query_attributes; + } + + /** + * decode a buffer into a message::client::StmtExecute. + * + * @param buffer a buffer + * @param caps protocol capabilities + * @param metadata_lookup callable that expects a 'uint32_t statement_id' + * that returns a result that's convertible to + * 'stdx::expected, std::error_code>' representing the + * parameter-definitions of the prepared statement + * + * decoding a StmtExecute message requires the parameter-definitions of the + * prepared statement. The metadata_lookup function may be called to get + * the parameter-definitions for the statement-id. + * + * The function may return a parameter-definitions directly + * + * \code + * StmtExecute::decode( + * buffers, + * capabilities::protocol_41, + * [](uint32_t stmt_id) { return std::vector{}; }); + * \endcode + * + * ... or a stdx::expected, std::error_code> if it wants + * to signal that a statement-id wasn't found + * + * \code + * StmtExecute::decode( + * buffers, + * capabilities::protocol_41, + * [](uint32_t stmt_id) -> + * stdx::expected, std::error_code> { + * bool found{true}; + * + * if (found) { + * return {}; + * } else { + * return stdx::unexpected(make_error_code( + * codec_errc::statement_id_not_found)); + * } + * }); + * \endcode + */ + template + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps, + Func &&metadata_lookup) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_id_res = accu.template step>(); + auto flags_res = accu.template step>(); + auto iteration_count_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + const auto param_count_available{1 << cursor::pos::param_count_available}; + + const bool supports_query_attributes = + caps.test(capabilities::pos::query_attributes); + + stdx::expected, std::error_code> + metadata_res = metadata_lookup(statement_id_res->value()); + if (!metadata_res) { + return stdx::unexpected( + make_error_code(codec_errc::statement_id_not_found)); + } + + size_t param_count = metadata_res->size(); + + if (supports_query_attributes && + (flags_res->value() & param_count_available) != 0) { + auto param_count_res = accu.template step(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + if (static_cast(param_count_res->value()) < param_count) { + // can the param-count shrink? + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + param_count = param_count_res->value(); + } + + if (param_count == 0) { + return std::make_pair( + accu.result().value(), + value_type(statement_id_res->value(), flags_res->value(), + iteration_count_res->value(), false, {}, {})); + } + + auto nullbits_res = + accu.template step>(bytes_per_bits(param_count)); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto new_params_bound_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + std::vector types; + + auto new_params_bound = new_params_bound_res->value(); + if (new_params_bound == 0) { + // no new params, use the last known params. + types = *metadata_res; + } else if (new_params_bound == 1) { + // check that there is at least enough data for the types (a FixedInt<2>) + // before reserving memory. + if (param_count >= buffer.size() / 2) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + types.reserve(param_count); + + for (size_t n{}; n < param_count; ++n) { + auto type_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (supports_query_attributes) { + auto name_res = accu.template step>(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + types.emplace_back(type_res->value(), name_res->value()); + } else { + types.emplace_back(type_res->value(), ""); + } + } + } else { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + if (param_count != types.size()) { + // param-count and available types doesn't match. + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + std::vector> values; + values.reserve(param_count); + + const auto nullbits = nullbits_res->value(); + for (size_t n{}, bit_pos{}, byte_pos{}; n < param_count; ++n, ++bit_pos) { + if (bit_pos > 7) { + bit_pos = 0; + ++byte_pos; + } + + // if the data was sent via COM_STMT_SEND_LONG_DATA, there will be no data + // for + const auto param_already_sent = + n < metadata_res->size() ? (*metadata_res)[n].param_already_sent + : false; + + if (param_already_sent) { + values.emplace_back(""); // empty + } else if (!(nullbits[byte_pos] & (1 << bit_pos))) { + stdx::expected field_size_res( + stdx::unexpected(make_error_code(std::errc::invalid_argument))); + switch (types[n].type_and_flags & 0xff) { + case field_type::Bit: + case field_type::Blob: + case field_type::Varchar: + case field_type::VarString: + case field_type::Set: + case field_type::String: + case field_type::Enum: + case field_type::TinyBlob: + case field_type::MediumBlob: + case field_type::LongBlob: + case field_type::Decimal: + case field_type::NewDecimal: + case field_type::Geometry: { + auto string_field_size_res = accu.template step(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + field_size_res = string_field_size_res->value(); + } break; + case field_type::Date: + case field_type::DateTime: + case field_type::Timestamp: + case field_type::Time: { + auto time_field_size_res = accu.template step>(); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + field_size_res = time_field_size_res->value(); + } break; + case field_type::LongLong: + case field_type::Double: + field_size_res = 8; + break; + case field_type::Long: + case field_type::Int24: + case field_type::Float: + field_size_res = 4; + break; + case field_type::Short: + case field_type::Year: + field_size_res = 2; + break; + case field_type::Tiny: + field_size_res = 1; + break; + } + + if (!field_size_res) { + return stdx::unexpected( + make_error_code(codec_errc::field_type_unknown)); + } + + auto value_res = + accu.template step>(field_size_res.value()); + if (!accu.result()) { + return stdx::unexpected(accu.result().error()); + } + + values.push_back(value_res->value()); + } else { + // NULL + values.emplace_back(std::nullopt); + } + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(statement_id_res->value(), flags_res->value(), + iteration_count_res->value(), new_params_bound_res->value(), + types, values)); + } + + private: + const value_type v_; +}; + +/** + * codec for client's append data Statement command. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .step(bw::FixedInt<2>(v_.param_id())) + .step(bw::String(v_.data())) + .result(); + } + + public: + using value_type = borrowable::message::client::StmtParamAppendData; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtSendLongData); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_id_res = accu.template step>(); + auto param_id_res = accu.template step>(); + auto data_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(statement_id_res->value(), + param_id_res->value(), data_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Close Statement command. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .result(); + } + + public: + using value_type = borrowable::message::client::StmtClose; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtClose); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_id_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(statement_id_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Reset Statement command. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .result(); + } + + public: + using value_type = borrowable::message::client::StmtReset; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtReset); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_id_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(statement_id_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's SetOption command. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<2>(v_.option())) + .result(); + } + + public: + using value_type = borrowable::message::client::SetOption; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::SetOption); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto option_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(option_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Fetch Cursor command. + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.statement_id())) + .step(bw::FixedInt<4>(v_.row_count())) + .result(); + } + + public: + using value_type = borrowable::message::client::StmtFetch; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::StmtFetch); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + auto statement_id_res = accu.template step>(); + auto row_count_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(statement_id_res->value(), row_count_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client side greeting message. + * + * + * in 3.21 ... 4.0: + * + * FixedInt<2> capabilities [protocol_41 not set] + * FixedInt<3> max-allowed-packet + * NulTermString username + * NulTermString auth-method-data + * + * [if not connect_with_schema, there may be no trailing Nul-byte] + * + * if connect_with_schema { + * String schema + * } + * + * the auth-method is "old_password" if "protocol_version == 10 && + * (capabilities & long_password)", it is "older_password" otherwise + * + * FixedInt<2> capabilities_lo [protocol_41 set] + * FixedInt<2> capabilities_hi + * FixedInt<4> max_allowed-packet + * ... + * + * The capabilities that are part of the message are the client's capabilities + * (which may announce more than what the server supports). The codec + * uses the capabilities that are shared between client and server to decide + * which parts and how they are understood, though. + * + * checked capabilities: + * - protocol_41 + * - ssl + * - client_auth_method_data_varint + * - secure_connection + * - connect_with_schema + * - plugin_auth + * - connect_attributes + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + const auto shared_caps = v_.capabilities() & this->caps(); + + if (shared_caps[classic_protocol::capabilities::pos::protocol_41]) { + accu.step(bw::FixedInt<4>(v_.capabilities().to_ulong())) + .step(bw::FixedInt<4>(v_.max_packet_size())) + .step(bw::FixedInt<1>(v_.collation())) + .step(bw::String(std::string(23, '\0'))); + if (!(shared_caps[classic_protocol::capabilities::pos::ssl] && + v_.username().empty())) { + // the username is empty and SSL is set, this is a short SSL-greeting + // packet + accu.step(bw::NulTermString(v_.username())); + + if (shared_caps[classic_protocol::capabilities::pos:: + client_auth_method_data_varint]) { + accu.step(bw::VarString(v_.auth_method_data())); + } else if (shared_caps[classic_protocol::capabilities::pos:: + secure_connection]) { + accu.step(bw::FixedInt<1>(v_.auth_method_data().size())) + .step(bw::String(v_.auth_method_data())); + } else { + accu.step(bw::NulTermString(v_.auth_method_data())); + } + + if (shared_caps + [classic_protocol::capabilities::pos::connect_with_schema]) { + accu.step(bw::NulTermString(v_.schema())); + } + + if (!shared_caps + [classic_protocol::capabilities::pos::connect_attributes]) { + // special handling for off-spec client/server implementations. + // + // 1. older clients may set ::plugin_auth, but + // ::connection_attributes which means nothing follows the + // "auth-method-name" field + // 2. auth-method-name is empty, it MAY be skipped. + if (shared_caps[classic_protocol::capabilities::pos::plugin_auth] && + !v_.auth_method_name().empty()) { + accu.step(bw::NulTermString(v_.auth_method_name())); + } + } else { + if (shared_caps[classic_protocol::capabilities::pos::plugin_auth]) { + accu.step(bw::NulTermString(v_.auth_method_name())); + } + + accu.step(bw::VarString(v_.attributes())); + } + } + } else { + accu.step(bw::FixedInt<2>(v_.capabilities().to_ulong())) + .step(bw::FixedInt<3>(v_.max_packet_size())) + .step(bw::NulTermString(v_.username())); + if (shared_caps + [classic_protocol::capabilities::pos::connect_with_schema]) { + accu.step(bw::NulTermString(v_.auth_method_data())) + .step(bw::String(v_.schema())); + } else { + accu.step(bw::String(v_.auth_method_data())); + } + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::client::Greeting; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::secure_connection | capabilities::protocol_41 | + capabilities::ssl | capabilities::client_auth_method_data_varint | + capabilities::connect_attributes | capabilities::connect_with_schema; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto capabilities_lo_res = accu.template step>(); + if (!capabilities_lo_res) + return stdx::unexpected(capabilities_lo_res.error()); + + auto client_capabilities = classic_protocol::capabilities::value_type( + capabilities_lo_res->value()); + + // decoding depends on the capabilities that both client and server have + // in common + auto shared_capabilities = caps & client_capabilities; + + if (shared_capabilities[classic_protocol::capabilities::pos::protocol_41]) { + // if protocol_41 is set in the capabilities, we expected 2 more bytes + // of capabilities + auto capabilities_hi_res = accu.template step>(); + if (!capabilities_hi_res) + return stdx::unexpected(capabilities_hi_res.error()); + + client_capabilities |= classic_protocol::capabilities::value_type( + capabilities_hi_res->value() << 16); + + shared_capabilities = caps & client_capabilities; + + auto max_packet_size_res = accu.template step>(); + auto collation_res = accu.template step>(); + + accu.template step>(23); // skip 23 bytes + + auto last_accu_res = accu.result(); + + auto username_res = accu.template step>(); + if (!accu.result()) { + // if there isn't enough data for the nul-term-string, but we had the + // 23-bytes ... + if (last_accu_res && + shared_capabilities[classic_protocol::capabilities::pos::ssl]) { + return std::make_pair( + last_accu_res.value(), + value_type(client_capabilities, max_packet_size_res->value(), + collation_res->value(), {}, {}, {}, {}, {})); + } + + return stdx::unexpected(accu.result().error()); + } + + // auth-method-data is either + // + // - varint length + // - fixed-int-1 length + // - null-term-string + stdx::expected, std::error_code> + auth_method_data_res; + if (shared_capabilities[classic_protocol::capabilities::pos:: + client_auth_method_data_varint]) { + auto res = accu.template step>(); + if (!res) return stdx::unexpected(res.error()); + + auth_method_data_res = bw::String(res->value()); + } else if (shared_capabilities + [classic_protocol::capabilities::pos::secure_connection]) { + auto auth_method_data_len_res = accu.template step>(); + if (!auth_method_data_len_res) + return stdx::unexpected(auth_method_data_len_res.error()); + auto auth_method_data_len = auth_method_data_len_res->value(); + + auto res = + accu.template step>(auth_method_data_len); + if (!res) return stdx::unexpected(res.error()); + + auth_method_data_res = bw::String(res->value()); + } else { + auto res = accu.template step>(); + if (!res) return stdx::unexpected(res.error()); + + auth_method_data_res = bw::String(res->value()); + } + + stdx::expected, std::error_code> schema_res; + if (shared_capabilities + [classic_protocol::capabilities::pos::connect_with_schema]) { + schema_res = accu.template step>(); + } + if (!schema_res) return stdx::unexpected(schema_res.error()); + + stdx::expected, std::error_code> + auth_method_res; + if (shared_capabilities + [classic_protocol::capabilities::pos::plugin_auth]) { + if (net::buffer_size(buffer) == accu.result().value()) { + // even with plugin_auth set, the server is fine, if no + // auth_method_name is sent. + auth_method_res = bw::NulTermString{}; + } else { + auth_method_res = accu.template step>(); + } + } + if (!auth_method_res) return stdx::unexpected(auth_method_res.error()); + + stdx::expected, std::error_code> attributes_res; + if (shared_capabilities + [classic_protocol::capabilities::pos::connect_attributes]) { + attributes_res = accu.template step>(); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(client_capabilities, max_packet_size_res->value(), + collation_res->value(), username_res->value(), + auth_method_data_res->value(), schema_res->value(), + auth_method_res->value(), attributes_res->value())); + + } else { + auto max_packet_size_res = accu.template step>(); + + auto username_res = accu.template step>(); + + stdx::expected, std::error_code> + auth_method_data_res; + stdx::expected, std::error_code> schema_res; + + if (shared_capabilities + [classic_protocol::capabilities::pos::connect_with_schema]) { + auto res = accu.template step>(); + if (!res) return stdx::unexpected(res.error()); + + // auth_method_data is a wire::String, move it over + auth_method_data_res = bw::String(res->value()); + + schema_res = accu.template step>(); + } else { + auth_method_data_res = accu.template step>(); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // idea: benchmark in-place constructor where all parameters are passed + // down to the lowest level. + // + // It should involve less copy-construction. + // + // - stdx::in_place is for in-place construction of stdx::expected's + // value + // - std::piecewise_construct for the parts of the std::pair that is + // returned as value of stdx::expected + // + // return {stdx::in_place, std::piecewise_construct, + // std::forward_as_tuple(accu.result().value()), + // std::forward_as_tuple(capabilities, + // max_packet_size_res->value(), + // 0x00, username_res->value(), + // auth_method_data_res->value(), + // schema_res->value(), {}, + // {})}; + return std::make_pair( + accu.result().value(), + value_type(client_capabilities, max_packet_size_res->value(), 0x00, + username_res->value(), auth_method_data_res->value(), + schema_res->value(), {}, {})); + } + } + + private: + const value_type v_; +}; + +/** + * codec for client::AuthMethodData message. + * + * format: + * + * - String auth_method_data + * + * sent after server::AuthMethodData or server::AuthMethodContinue + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::String(v_.auth_method_data())).result(); + } + + public: + using value_type = borrowable::message::client::AuthMethodData; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto auth_method_data_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(auth_method_data_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client side change-user message. + * + * checked capabilities: + * - protocol_41 + * - secure_connection + * - plugin_auth + * - connect_attributes + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::NulTermString(v_.username())); + + if (this->caps()[classic_protocol::capabilities::pos::secure_connection]) { + accu.step(bw::FixedInt<1>(v_.auth_method_data().size())) + .step(bw::String(v_.auth_method_data())); + } else { + accu.step(bw::NulTermString(v_.auth_method_data())); + } + accu.step(bw::NulTermString(v_.schema())); + + // 4.1 and later have a collation + // + // this could be checked via the protocol_41 capability, but that's not + // what the server does + if (v_.collation() != 0x00 || + this->caps()[classic_protocol::capabilities::pos::plugin_auth] || + this->caps()[classic_protocol::capabilities::pos::connect_attributes]) { + accu.step(bw::FixedInt<2>(v_.collation())); + if (this->caps()[classic_protocol::capabilities::pos::plugin_auth]) { + accu.step(bw::NulTermString(v_.auth_method_name())); + } + + if (this->caps() + [classic_protocol::capabilities::pos::connect_attributes]) { + accu.step(bw::VarString(v_.attributes())); + } + } + + return accu.result(); + } + + public: + using value_type = borrowable::message::client::ChangeUser; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::ChangeUser); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return capabilities::secure_connection | capabilities::plugin_auth | + capabilities::connect_attributes; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auto username_res = accu.template step>(); + + // auth-method-data is either + // + // - fixed-int-1 length + // - null-term-string + stdx::expected, std::error_code> auth_method_data_res; + if (caps[classic_protocol::capabilities::pos::secure_connection]) { + auto auth_method_data_len_res = accu.template step>(); + if (!auth_method_data_len_res) + return stdx::unexpected(auth_method_data_len_res.error()); + auto auth_method_data_len = auth_method_data_len_res->value(); + + auto res = accu.template step>(auth_method_data_len); + if (!res) return stdx::unexpected(res.error()); + + auth_method_data_res = bw::String(res->value()); + } else { + auto res = accu.template step>(); + if (!res) return stdx::unexpected(res.error()); + + auth_method_data_res = bw::String(res->value()); + } + + auto schema_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // 3.23.x-4.0 don't send more. + if (buffer_size(buffer) <= accu.result().value()) { + return std::make_pair( + accu.result().value(), + value_type(username_res->value(), auth_method_data_res->value(), + schema_res->value(), 0x00, {}, {})); + } + + // added in 4.1 + auto collation_res = accu.template step>(); + + stdx::expected, std::error_code> + auth_method_name_res; + if (caps[classic_protocol::capabilities::pos::plugin_auth]) { + auth_method_name_res = accu.template step>(); + } + + stdx::expected, std::error_code> attributes_res; + if (caps[classic_protocol::capabilities::pos::connect_attributes]) { + attributes_res = accu.template step>(); + } + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(username_res->value(), auth_method_data_res->value(), + schema_res->value(), collation_res->value(), + auth_method_name_res->value(), attributes_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client's Clone command. + * + * response: server::Ok or server::Error + */ +template <> +class Codec + : public CodecSimpleCommand, + borrowable::message::client::Clone> { + public: + using value_type = borrowable::message::client::Clone; + using __base = CodecSimpleCommand, value_type>; + + constexpr Codec(value_type, capabilities::value_type caps) : __base(caps) {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::Clone); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } +}; + +/** + * codec for client side dump-binlog message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + public: + using value_type = borrowable::message::client::BinlogDump; + + private: + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.position())) + .step(bw::FixedInt<2>(v_.flags().underlying_value())) + .step(bw::FixedInt<4>(v_.server_id())) + .step(bw::String(v_.filename())) + .result(); + } + + public: + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::BinlogDump); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auto position_res = accu.template step>(); + auto flags_res = accu.template step>(); + auto server_id_res = accu.template step>(); + + auto filename_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + stdx::flags flags; + flags.underlying_value(flags_res->value()); + + return std::make_pair( + accu.result().value(), + value_type(flags, server_id_res->value(), filename_res->value(), + position_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client side register-replica message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + public: + using value_type = borrowable::message::client::RegisterReplica; + + private: + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<4>(v_.server_id())) + .step(bw::FixedInt<1>(v_.hostname().size())) + .step(bw::String(v_.hostname())) + .step(bw::FixedInt<1>(v_.username().size())) + .step(bw::String(v_.username())) + .step(bw::FixedInt<1>(v_.password().size())) + .step(bw::String(v_.password())) + .step(bw::FixedInt<2>(v_.port())) + .step(bw::FixedInt<4>(v_.replication_rank())) + .step(bw::FixedInt<4>(v_.master_id())) + .result(); + } + + public: + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::RegisterReplica); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auto server_id_res = accu.template step>(); + auto hostname_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto hostname_res = + accu.template step>(hostname_len_res->value()); + + auto username_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto username_res = + accu.template step>(username_len_res->value()); + + auto password_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto password_res = + accu.template step>(password_len_res->value()); + + auto port_res = accu.template step>(); + auto replication_rank_res = accu.template step>(); + auto master_id_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(server_id_res->value(), hostname_res->value(), + username_res->value(), password_res->value(), + port_res->value(), replication_rank_res->value(), + master_id_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for client side dump-binlog-with-gtid message. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + public: + using value_type = borrowable::message::client::BinlogDumpGtid; + + private: + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + accu.step(bw::FixedInt<1>(cmd_byte())) + .step(bw::FixedInt<2>(v_.flags().underlying_value())) + .step(bw::FixedInt<4>(v_.server_id())) + .step(bw::FixedInt<4>(v_.filename().size())) + .step(bw::String(v_.filename())) + .step(bw::FixedInt<8>(v_.position())); + + if (v_.flags() & value_type::Flags::through_gtid) { + accu.step(bw::FixedInt<4>(v_.sids().size())) + .step(bw::String(v_.sids())); + } + + return accu.result(); + } + + public: + using base_ = impl::EncodeBase>; + + friend base_; + + constexpr Codec(value_type val, capabilities::value_type caps) + : base_(caps), v_{std::move(val)} {} + + constexpr static uint8_t cmd_byte() noexcept { + return static_cast(CommandByte::BinlogDumpGtid); + } + + /** + * capabilities the codec depends on. + */ + static constexpr capabilities::value_type depends_on_capabilities() noexcept { + return {}; + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto cmd_byte_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (cmd_byte_res->value() != cmd_byte()) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + auto flags_res = accu.template step>(); + auto server_id_res = accu.template step>(); + auto filename_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto filename_res = + accu.template step>(filename_len_res->value()); + auto position_res = accu.template step>(); + + stdx::flags flags; + flags.underlying_value(flags_res->value()); + + if (!(flags & value_type::Flags::through_gtid)) { + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(flags, server_id_res->value(), filename_res->value(), + position_res->value(), {})); + } + + auto sids_len_res = accu.template step>(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + auto sids_res = + accu.template step>(sids_len_res->value()); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(flags, server_id_res->value(), filename_res->value(), + position_res->value(), sids_res->value())); + } + + private: + const value_type v_; +}; + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_session_track.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_session_track.h new file mode 100644 index 0000000..4c530b0 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_session_track.h @@ -0,0 +1,491 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_SESSION_TRACK_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_SESSION_TRACK_H_ + +// codecs for classic_protocol::session_track:: messages + +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_codec_wire.h" +#include "mysqlrouter/classic_protocol_session_track.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { + +/** + * codec for session_track::TransactionState. + * + * part of session_track::Field + */ +template <> +class Codec + : public impl::EncodeBase< + Codec> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu + .step(bw::FixedInt<1>(0x08)) // length + .step(bw::FixedInt<1>(v_.trx_type())) + .step(bw::FixedInt<1>(v_.read_unsafe())) + .step(bw::FixedInt<1>(v_.read_trx())) + .step(bw::FixedInt<1>(v_.write_unsafe())) + .step(bw::FixedInt<1>(v_.write_trx())) + .step(bw::FixedInt<1>(v_.stmt_unsafe())) + .step(bw::FixedInt<1>(v_.resultset())) + .step(bw::FixedInt<1>(v_.locked_tables())) + .result(); + } + + public: + using value_type = borrowable::session_track::TransactionState; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x05; } + + /** + * decode a session_track::TransactionState from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + const auto payload_length_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + if (payload_length_res->value() != 0x08) { + // length of the payload that follows. + return stdx::unexpected(make_error_code(std::errc::bad_message)); + } + + const auto trx_type_res = accu.template step>(); + const auto read_unsafe_res = accu.template step>(); + const auto read_trx_res = accu.template step>(); + const auto write_unsafe_res = accu.template step>(); + const auto write_trx_res = accu.template step>(); + const auto stmt_unsafe_res = accu.template step>(); + const auto resultset_res = accu.template step>(); + const auto locked_tables_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair( + accu.result().value(), + value_type(trx_type_res->value(), read_unsafe_res->value(), + read_trx_res->value(), write_unsafe_res->value(), + write_trx_res->value(), stmt_unsafe_res->value(), + resultset_res->value(), locked_tables_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session_track::TransactionCharacteristics. + * + * part of session_track::Field + */ +template +class Codec> + : public impl::EncodeBase>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::VarString(v_.characteristics())).result(); + } + + public: + using value_type = + borrowable::session_track::TransactionCharacteristics; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x04; } + + /** + * decode a session_track::TransactionCharacteristics from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on + * success, with bytes processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto characteristics_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(characteristics_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session_track::State. + * + * part of session_track::Field + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(v_.state())).result(); + } + + public: + using value_type = session_track::State; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x02; } + + /** + * decode a session_track::State from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto state_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(state_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session_track::Schema. + * + * part of session_track::Field + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::VarString(v_.schema())).result(); + } + + public: + using value_type = borrowable::session_track::Schema; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x01; } + + /** + * decode a session_track::Schema from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto schema_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(schema_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session_track::SystemVariable. + * + * part of session_track::Field + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::VarString(v_.key())) + .step(bw::VarString(v_.value())) + .result(); + } + + public: + using value_type = borrowable::session_track::SystemVariable; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x00; } + + /** + * decode a session_track::SystemVariable from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with + * bytes processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto key_res = accu.template step>(); + auto value_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(key_res->value(), value_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session_track::Gtid. + * + * format: + * + * - FixedInt spec (only 0 is in use for now) + * - VarString payload (payload according to spec). + * + * payload for spec 0: + * - GTID in human-readable form like 4dd0f9d5-3b00-11eb-ad70-003093140e4e:23929 + * + * part of session_track::Field + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(v_.spec())) + .step(bw::VarString(v_.gtid())) + .result(); + } + + public: + using value_type = borrowable::session_track::Gtid; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + constexpr static uint8_t type_byte() { return 0x03; } + + /** + * decode a session_track::Gtid from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto spec_res = accu.template step>(); + auto gtid_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(spec_res->value(), gtid_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for session-track's Field. + * + * sent as part of a server::Ok and server::Eof message. + * + * - FixedInt<1> type + * - VarString data + * + * data is encoded according type: + * + * - 0x00 session_track::SystemVariable + * - 0x01 session_track::Schema + * - 0x02 session_track::StateChanged + * - 0x03 session_track::Gtid + * - 0x04 session_track::TransactionCharacteristics + * - 0x05 session_track::TransactionState + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + namespace bw = borrowable::wire; + + return accu.step(bw::FixedInt<1>(v_.type())) + .step(bw::VarString(v_.data())) + .result(); + } + + public: + using value_type = borrowable::session_track::Field; + + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base(caps), v_{std::move(v)} {} + + /** + * decode a session_track::Field from a buffer-sequence. + * + * @param buffer input buffer + * @param caps protocol capabilities + * + * @retval std::pair on success, with bytes + * processed + * @retval codec_errc::not_enough_input not enough data to parse the whole + * message + */ + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + namespace bw = borrowable::wire; + + auto type_res = accu.template step>(); + auto data_res = accu.template step>(); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(type_res->value(), data_res->value())); + } + + private: + const value_type v_; +}; + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_wire.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_wire.h new file mode 100644 index 0000000..caa0b71 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_codec_wire.h @@ -0,0 +1,444 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_WIRE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CODEC_WIRE_H_ + +// codecs for classic_protocol::wire:: + +#include // find +#include // endian +#include // size_t +#include // uint8_t +#include // error_code +#include +#include // move + +#include "mysql/harness/net_ts/buffer.h" +#include "mysql/harness/stdx/bit.h" // byteswap +#include "mysql/harness/stdx/expected.h" +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_error.h" +#include "mysqlrouter/classic_protocol_wire.h" + +namespace classic_protocol { +/** + * codec of a FixedInt. + * + * classic proto uses 1, 2, 3, 4, 8 for IntSize + */ +template +class Codec> { + public: + static constexpr size_t int_size{IntSize}; + + using value_type = borrowable::wire::FixedInt; + + constexpr Codec(value_type v, capabilities::value_type /* caps */) : v_{v} {} + + /** + * size of the encoded object. + */ + static constexpr size_t size() noexcept { return int_size; } + + /** + * encode value_type into buffer. + */ + stdx::expected encode( + net::mutable_buffer buffer) const { + if (buffer.size() < int_size) { + return stdx::unexpected(make_error_code(std::errc::no_buffer_space)); + } + + auto int_val = v_.value(); + + if (std::endian::native == std::endian::big) { + int_val = stdx::byteswap(int_val); + } + + std::copy_n(reinterpret_cast(&int_val), int_size, + static_cast(buffer.data())); + + return int_size; + } + + /** + * maximum bytes which may scanned by the decoder. + */ + static constexpr size_t max_size() noexcept { return int_size; } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type /* caps */) { + if (buffer.size() < int_size) { + // not enough data in buffers. + return stdx::unexpected(make_error_code(codec_errc::not_enough_input)); + } + + typename value_type::value_type value{}; + + std::copy_n(static_cast(buffer.data()), int_size, + reinterpret_cast(&value)); + + if (std::endian::native == std::endian::big) { + value = stdx::byteswap(value); + } + + return std::make_pair(int_size, value_type(value)); + } + + private: + const value_type v_; +}; + +/** + * codec for variable length integers. + * + * note: encoded as little endian + * + * 0x00 + * ... + * 0xfa -> 0xfa + * 0xfb [undefined] + * 0xfc 0x.. 0x.. + * 0xfd 0x.. 0x.. 0x.. + * + * 3.21: + * 0xfe 0x.. 0x.. 0x.. 0x.. 0x00 + * [1 + 5 bytes read, only 4 bytes used] + * + * 4.0: + * 0xfe 0x.. 0x.. 0x.. 0x.. 0x.. 0x.. 0x.. 0x.. + * [1 + 8 bytes read, only 4 bytes used] + */ +template <> +class Codec + : public impl::EncodeBase> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + if (v_.value() < 251) { + return accu.step(borrowable::wire::FixedInt<1>(v_.value())).result(); + } else if (v_.value() < 1 << 16) { + return accu.step(borrowable::wire::FixedInt<1>(varint_16)) + .step(borrowable::wire::FixedInt<2>(v_.value())) + .result(); + } else if (v_.value() < (1 << 24)) { + return accu.step(borrowable::wire::FixedInt<1>(varint_24)) + .step(borrowable::wire::FixedInt<3>(v_.value())) + .result(); + } else { + return accu.step(borrowable::wire::FixedInt<1>(varint_64)) + .step(borrowable::wire::FixedInt<8>(v_.value())) + .result(); + } + } + + public: + static constexpr uint8_t varint_16{0xfc}; + static constexpr uint8_t varint_24{0xfd}; + static constexpr uint8_t varint_64{0xfe}; + using value_type = borrowable::wire::VarInt; + using __base = impl::EncodeBase>; + + friend __base; + + constexpr Codec(value_type v, capabilities::value_type caps) + : __base{caps}, v_{v} {} + + static constexpr size_t max_size() noexcept { return 9; } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + + // length + auto first_byte_res = accu.template step>(); + if (!first_byte_res) return stdx::unexpected(first_byte_res.error()); + + auto first_byte = first_byte_res->value(); + + if (first_byte < 251) { + return std::make_pair(accu.result().value(), value_type(first_byte)); + } else if (first_byte == varint_16) { + auto value_res = accu.template step>(); + if (!value_res) return stdx::unexpected(value_res.error()); + return std::make_pair(accu.result().value(), + value_type(value_res->value())); + } else if (first_byte == varint_24) { + auto value_res = accu.template step>(); + if (!value_res) return stdx::unexpected(value_res.error()); + return std::make_pair(accu.result().value(), + value_type(value_res->value())); + } else if (first_byte == varint_64) { + auto value_res = accu.template step>(); + if (!value_res) return stdx::unexpected(value_res.error()); + return std::make_pair(accu.result().value(), + value_type(value_res->value())); + } + + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + private: + const value_type v_; +}; + +/** + * codec for a NULL value in the Resultset. + */ +template <> +class Codec + : public Codec> { + public: + using value_type = borrowable::wire::Null; + + static constexpr uint8_t nul_byte{0xfb}; + + Codec(value_type, capabilities::value_type caps) + : Codec>(nul_byte, caps) {} + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type /* caps */) { + if (buffer.size() < 1) { + return stdx::unexpected(make_error_code(codec_errc::not_enough_input)); + } + + const uint8_t nul_val = *static_cast(buffer.data()); + + if (nul_val != nul_byte) { + return stdx::unexpected(make_error_code(codec_errc::invalid_input)); + } + + return std::make_pair(1, value_type()); + } +}; + +/** + * codec for ignorable bytes. + * + * limited by length or buffer.size() + */ +template <> +class Codec { + public: + using value_type = size_t; + + Codec(value_type val, capabilities::value_type caps) : v_(val), caps_{caps} {} + + size_t size() const noexcept { return v_; } + + static size_t max_size() noexcept { + return std::numeric_limits::max(); + } + + stdx::expected encode( + const net::mutable_buffer &buffer) const { + if (buffer.size() < size()) { + return stdx::unexpected(make_error_code(std::errc::no_buffer_space)); + } + + auto *first = static_cast(buffer.data()); + auto *last = first + size(); + + // fill with 0 + std::fill(first, last, 0); + + return size(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type /* caps */) { + size_t buf_size = buffer.size(); + + return std::make_pair(buf_size, buf_size); + } + + private: + const value_type v_; + const capabilities::value_type caps_; +}; + +/** + * codec for wire::String. + * + * limited by length or buffer.size() + */ +template +class Codec> { + public: + using value_type = borrowable::wire::String; + + constexpr Codec(value_type v, capabilities::value_type caps) + : v_{std::move(v)}, caps_{caps} {} + + constexpr size_t size() const noexcept { return v_.value().size(); } + + static size_t max_size() noexcept { + // we actually don't know what the size of the null-term string is ... until + // the end of the buffer + return std::numeric_limits::max(); + } + + stdx::expected encode( + const net::mutable_buffer &buffer) const { + if (buffer.size() < size()) { + return stdx::unexpected(make_error_code(std::errc::no_buffer_space)); + } + + // in -> out + std::copy_n(reinterpret_cast(v_.value().data()), size(), + static_cast(buffer.data())); + + return size(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type /* caps */) { + const size_t buf_size = buffer_size(buffer); + + if (0 == buf_size) return std::make_pair(buf_size, value_type()); + + return std::make_pair( + buf_size, + value_type({static_cast(buffer.data()), buffer.size()})); + } + + private: + const value_type v_; + const capabilities::value_type caps_; +}; + +/** + * codec for string with known length. + * + * - varint of string length + * - string of length + */ +template +class Codec> + : public impl::EncodeBase>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.step(borrowable::wire::VarInt(v_.value().size())) + .step(borrowable::wire::String(v_.value())) + .result(); + } + + public: + using value_type = borrowable::wire::VarString; + using base_type = impl::EncodeBase>; + + friend base_type; + + constexpr Codec(value_type val, capabilities::value_type caps) + : base_type(caps), v_{std::move(val)} {} + + static size_t max_size() noexcept { + // we actually don't know what the size of the null-term string is ... + // until the end of the buffer + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type caps) { + impl::DecodeBufferAccumulator accu(buffer, caps); + // decode the length + auto var_string_len_res = accu.template step(); + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + // decode string of length + auto var_string_res = + accu.template step>( + var_string_len_res->value()); + + if (!accu.result()) return stdx::unexpected(accu.result().error()); + + return std::make_pair(accu.result().value(), + value_type(var_string_res->value())); + } + + private: + const value_type v_; +}; + +/** + * codec for 0-terminated string. + */ +template +class Codec> + : public impl::EncodeBase< + Codec>> { + template + constexpr auto accumulate_fields(Accumulator &&accu) const { + return accu.template step>(v_) + .template step>(0) + .result(); + } + + public: + using value_type = borrowable::wire::NulTermString; + using base_type = impl::EncodeBase>; + + friend base_type; + + constexpr Codec(value_type val, capabilities::value_type caps) + : base_type(caps), v_{std::move(val)} {} + + static size_t max_size() noexcept { + // we actually don't know what the size of the null-term string is ... + // until the end of the buffer + return std::numeric_limits::max(); + } + + static stdx::expected, std::error_code> decode( + const net::const_buffer &buffer, capabilities::value_type /* caps */) { + // length of the string before the \0 + + const auto *first = static_cast(buffer.data()); + const auto *last = first + buffer.size(); + + const auto *pos = std::find(first, last, '\0'); + if (pos == last) { + // no 0-term found + return stdx::unexpected(make_error_code(codec_errc::missing_nul_term)); + } + + // \0 was found + size_t len = std::distance(first, pos); + if (len == 0) { + return std::make_pair(len + 1, value_type()); // consume the \0 too + } + + return std::make_pair(len + 1, + value_type({static_cast(buffer.data()), + len})); // consume the \0 too + } + + private: + const value_type v_; +}; + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_constants.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_constants.h new file mode 100644 index 0000000..67db243 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_constants.h @@ -0,0 +1,341 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_CONSTANTS_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_CONSTANTS_H_ + +#include +#include + +namespace classic_protocol { + +namespace capabilities { + +namespace pos { +using value_type = uint8_t; +constexpr value_type long_password{0}; +constexpr value_type found_rows{1}; +constexpr value_type long_flag{2}; +constexpr value_type connect_with_schema{3}; +constexpr value_type no_schema{4}; +constexpr value_type compress{5}; +constexpr value_type odbc{6}; +constexpr value_type local_files{7}; +constexpr value_type ignore_space{8}; +constexpr value_type protocol_41{9}; +constexpr value_type interactive{10}; +constexpr value_type ssl{11}; +// 12 is unused +constexpr value_type transactions{13}; +// 14 is unused +constexpr value_type secure_connection{15}; +constexpr value_type multi_statements{16}; +constexpr value_type multi_results{17}; +constexpr value_type ps_multi_results{18}; +constexpr value_type plugin_auth{19}; +constexpr value_type connect_attributes{20}; +constexpr value_type client_auth_method_data_varint{21}; +constexpr value_type expired_passwords{22}; +constexpr value_type session_track{23}; +constexpr value_type text_result_with_session_tracking{24}; +constexpr value_type optional_resultset_metadata{25}; +constexpr value_type compress_zstd{26}; +constexpr value_type query_attributes{27}; +// +// 29 is an extension flag for >32 bit +// 30 is client only +// 31 is client only + +} // namespace pos + +using value_type = std::bitset<32>; + +// old_password instead of older_password +// version_added: 3.21 +constexpr value_type long_password{1 << pos::long_password}; +// found rows, instead of affected rows. +// version_added: 3.21 +constexpr value_type found_rows{1 << pos::found_rows}; +// version_added: 3.21 +// get all column flags +constexpr value_type long_flag{1 << pos::long_flag}; +// connect with schema +// version_added: 3.21 +constexpr value_type connect_with_schema{1 << pos::connect_with_schema}; +// don't allow schema.table.column +// version_added: 3.21 +constexpr value_type no_schema{1 << pos::no_schema}; +// use deflate compression +// version_added: 3.22 +constexpr value_type compress{1 << pos::compress}; +// odbc client +// version_added: 3.22 +constexpr value_type odbc{1 << pos::odbc}; +// can use LOCAL INFILE +// version_added: 3.22 +constexpr value_type local_files{1 << pos::local_files}; +// ignore space before ( +// version_added: 3.22 +constexpr value_type ignore_space{1 << pos::ignore_space}; +// protocol_version 10 + more fields in server::Greeting +// version_added: 4.1 +constexpr value_type protocol_41{1 << pos::protocol_41}; +// interactive +// version_added: 3.22 +constexpr value_type interactive{1 << pos::interactive}; +// switch to SSL +// version_added: 3.23 +constexpr value_type ssl{1 << pos::ssl}; +// status-field in Ok message +// version_added: 3.23 +constexpr value_type transactions{1 << pos::transactions}; +// mysql_native_password +// version_added: 4.1 +constexpr value_type secure_connection{1 << pos::secure_connection}; +// multi-statement support +// version_added: 4.1 +constexpr value_type multi_statements{1 << pos::multi_statements}; +// multi-result support +// version_added: 4.1 +constexpr value_type multi_results{1 << pos::multi_results}; +// version_added: 5.5 +constexpr value_type ps_multi_results{1 << pos::ps_multi_results}; +// version_added: 5.5 +constexpr value_type plugin_auth{1 << pos::plugin_auth}; +// version_added: 5.6 +constexpr value_type connect_attributes{1 << pos::connect_attributes}; +// version_added: 5.6 +constexpr value_type client_auth_method_data_varint{ + 1 << pos::client_auth_method_data_varint}; +// version_added: 5.6 +constexpr value_type expired_passwords{1 << pos::expired_passwords}; +// version_added: 5.7 +constexpr value_type session_track{1 << pos::session_track}; +// version_added: 5.7 +constexpr value_type text_result_with_session_tracking{ + 1 << pos::text_result_with_session_tracking}; +// version_added: 8.0 +constexpr value_type compress_zstd{1 << pos::compress_zstd}; +// version_added: 8.0 +constexpr value_type optional_resultset_metadata{ + 1 << pos::optional_resultset_metadata}; +// version_added: 8.0 +constexpr value_type query_attributes{1 << pos::query_attributes}; +} // namespace capabilities + +namespace status { +namespace pos { +using value_type = uint8_t; +constexpr value_type in_transaction{0}; +constexpr value_type autocommit{1}; +// 2 is unused (more-results in 4.1.22) +constexpr value_type more_results_exist{3}; +constexpr value_type no_good_index_used{4}; +constexpr value_type no_index_used{5}; +constexpr value_type cursor_exists{6}; +constexpr value_type last_row_sent{7}; +constexpr value_type schema_dropped{8}; +constexpr value_type no_backslash_escapes{9}; +constexpr value_type metadata_changed{10}; +constexpr value_type query_was_slow{11}; +constexpr value_type ps_out_params{12}; +constexpr value_type in_transaction_readonly{13}; +constexpr value_type session_state_changed{14}; + +} // namespace pos +using value_type = std::bitset<16>; + +// transaction is open +// version_added: 3.23 +constexpr value_type in_transaction{1 << pos::in_transaction}; +// autocommit +// version_added: 3.23 +constexpr value_type autocommit{1 << pos::autocommit}; +// multi-statement more results +// version_added: 4.1 +constexpr value_type more_results_exist{1 << pos::more_results_exist}; +// no good index used +// version_added: 4.1 +constexpr value_type no_good_index_used{1 << pos::no_good_index_used}; +// no index used +// version_added: 4.1 +constexpr value_type no_index_used{1 << pos::no_index_used}; +// cursor exists +// version_added: 5.0 +constexpr value_type cursor_exists{1 << pos::cursor_exists}; +// last row sent +// version_added: 5.0 +constexpr value_type last_row_sent{1 << pos::last_row_sent}; +// schema dropped +// version_added: 4.1 +constexpr value_type schema_dropped{1 << pos::schema_dropped}; +// no backslash escapes +// version_added: 5.0 +constexpr value_type no_backslash_escapes{1 << pos::no_backslash_escapes}; +// metadata changed +// version_added: 5.1 +constexpr value_type metadata_changed{1 << pos::metadata_changed}; +// version_added: 5.5 +constexpr value_type query_was_slow{1 << pos::query_was_slow}; +// version_added: 5.5 +constexpr value_type ps_out_params{1 << pos::ps_out_params}; +// version_added: 5.7 +constexpr value_type in_transaction_readonly{1 << pos::in_transaction_readonly}; +// version_added: 5.7 +constexpr value_type session_state_changed{1 << pos::session_state_changed}; +} // namespace status + +namespace cursor { +namespace pos { +using value_type = uint8_t; +constexpr value_type read_only{0}; +constexpr value_type for_update{1}; +constexpr value_type scrollable{2}; +constexpr value_type param_count_available{3}; + +constexpr value_type _bitset_size{param_count_available + 1}; +} // namespace pos +using value_type = std::bitset; + +constexpr value_type no_cursor{0}; +constexpr value_type read_only{1 << pos::read_only}; +constexpr value_type for_update{1 << pos::for_update}; +constexpr value_type scrollable{1 << pos::scrollable}; +constexpr value_type param_count_available{1 << pos::param_count_available}; +} // namespace cursor + +namespace field_type { +using value_type = uint8_t; +constexpr value_type Decimal{0x00}; +constexpr value_type Tiny{0x01}; +constexpr value_type Short{0x02}; +constexpr value_type Long{0x03}; +constexpr value_type Float{0x04}; +constexpr value_type Double{0x05}; +constexpr value_type Null{0x06}; +constexpr value_type Timestamp{0x07}; +constexpr value_type LongLong{0x08}; +constexpr value_type Int24{0x09}; +constexpr value_type Date{0x0a}; +constexpr value_type Time{0x0b}; +constexpr value_type DateTime{0x0c}; +constexpr value_type Year{0x0d}; +// not used in protocol: constexpr value_type NewDate{0x0e}; +constexpr value_type Varchar{0x0f}; +constexpr value_type Bit{0x10}; +constexpr value_type Timestamp2{0x11}; +// not used in protocol: constexpr value_type Datetime2{0x12}; +// not used in protocol: constexpr value_type Time2{0x13}; +// not used in protocol: constexpr value_type TypedArray{0x14}; +constexpr value_type Json{0xf5}; +constexpr value_type NewDecimal{0xf6}; +constexpr value_type Enum{0xf7}; +constexpr value_type Set{0xf8}; +constexpr value_type TinyBlob{0xf9}; +constexpr value_type MediumBlob{0xfa}; +constexpr value_type LongBlob{0xfb}; +constexpr value_type Blob{0xfc}; +constexpr value_type VarString{0xfd}; +constexpr value_type String{0xfe}; +constexpr value_type Geometry{0xff}; +} // namespace field_type + +namespace column_def { +namespace pos { +using value_type = uint8_t; +constexpr value_type not_null{0}; +constexpr value_type primary_key{1}; +constexpr value_type unique_key{2}; +constexpr value_type multiple_key{3}; +constexpr value_type blob{4}; +constexpr value_type is_unsigned{5}; +constexpr value_type zerofill{6}; +constexpr value_type binary{7}; +constexpr value_type is_enum{8}; +constexpr value_type auto_increment{9}; +constexpr value_type timestamp{10}; +constexpr value_type set{11}; +constexpr value_type no_default_value{12}; +constexpr value_type on_update{13}; +constexpr value_type numeric{14}; + +constexpr value_type _bitset_size{numeric + 1}; +} // namespace pos +using value_type = std::bitset; + +constexpr value_type not_null{1 << pos::not_null}; +constexpr value_type primary_key{1 << pos::primary_key}; +constexpr value_type unique_key{1 << pos::unique_key}; +constexpr value_type multiple_key{1 << pos::multiple_key}; +constexpr value_type blob{1 << pos::blob}; +constexpr value_type is_unsigned{1 << pos::is_unsigned}; +constexpr value_type zerofill{1 << pos::zerofill}; +constexpr value_type binary{1 << pos::binary}; +constexpr value_type is_enum{1 << pos::is_enum}; +constexpr value_type auto_increment{1 << pos::auto_increment}; +constexpr value_type timestamp{1 << pos::timestamp}; +constexpr value_type set{1 << pos::set}; +constexpr value_type no_default_value{1 << pos::no_default_value}; +constexpr value_type on_update{1 << pos::on_update}; +constexpr value_type numeric{1 << pos::numeric}; +} // namespace column_def + +namespace reload_cmds { +namespace pos { +using value_type = uint8_t; +constexpr value_type flush_privileges{0}; +constexpr value_type flush_logs{1}; +constexpr value_type flush_tables{2}; +constexpr value_type flush_hosts{3}; +constexpr value_type flush_status{4}; +constexpr value_type flush_threads{5}; +constexpr value_type reset_slave{6}; +constexpr value_type reset_master{7}; + +constexpr value_type _bitset_size{reset_master + 1}; +} // namespace pos +using value_type = std::bitset; + +constexpr value_type flush_privileges{1 << pos::flush_privileges}; +constexpr value_type flush_logs{1 << pos::flush_logs}; +constexpr value_type flush_tables{1 << pos::flush_tables}; +constexpr value_type flush_hosts{1 << pos::flush_hosts}; +constexpr value_type flush_status{1 << pos::flush_status}; +constexpr value_type flush_threads{1 << pos::flush_threads}; +constexpr value_type reset_slave{1 << pos::reset_slave}; +constexpr value_type reset_master{1 << pos::reset_master}; + +} // namespace reload_cmds + +namespace collation { +using value_type = uint8_t; +constexpr value_type Latin1SwedishCi{0x08}; +constexpr value_type Utf8GeneralCi{0x21}; +constexpr value_type Binary{0x3f}; +} // namespace collation + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_frame.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_frame.h new file mode 100644 index 0000000..ea1f981 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_frame.h @@ -0,0 +1,109 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_FRAME_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_FRAME_H_ + +#include // size_t +#include // uint8_t +#include // move + +namespace classic_protocol { + +namespace frame { +class Header { + public: + constexpr Header(size_t payload_size, uint8_t seq_id) noexcept + : payload_size_{payload_size}, seq_id_{seq_id} {} + + constexpr size_t payload_size() const noexcept { return payload_size_; } + constexpr uint8_t seq_id() const noexcept { return seq_id_; } + + private: + size_t payload_size_{0}; + uint8_t seq_id_{0}; +}; + +constexpr bool operator==(const Header &a, const Header &b) { + return a.payload_size() == b.payload_size() && a.seq_id() == b.seq_id(); +} + +/** + * header of a compressed frame. + * + * used if client and server negotiated compression. + */ +class CompressedHeader { + public: + constexpr CompressedHeader(size_t payload_size, uint8_t seq_id, + size_t uncompressed_size) + : payload_size_{payload_size}, + seq_id_{seq_id}, + uncompressed_size_{uncompressed_size} {} + + constexpr size_t payload_size() const noexcept { return payload_size_; } + constexpr uint8_t seq_id() const noexcept { return seq_id_; } + constexpr size_t uncompressed_size() const noexcept { + return uncompressed_size_; + } + + private: + size_t payload_size_; + uint8_t seq_id_; + size_t uncompressed_size_; +}; + +constexpr bool operator==(const CompressedHeader &a, + const CompressedHeader &b) { + return a.payload_size() == b.payload_size() && a.seq_id() == b.seq_id() && + a.uncompressed_size() == b.uncompressed_size(); +} + +template +class Frame { + public: + using value_type = PayloadType; + + constexpr Frame(uint8_t seq_id, value_type v) + : seq_id_{seq_id}, payload_{std::move(v)} {} + + constexpr uint8_t seq_id() const { return seq_id_; } + constexpr value_type payload() const { return payload_; } + + private: + uint8_t seq_id_; + value_type payload_; +}; + +template +bool operator==(const Frame &a, const Frame &b) { + return a.seq_id() == b.seq_id() && a.payload() == b.payload(); +} + +} // namespace frame + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_message.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_message.h new file mode 100644 index 0000000..59b326b --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_message.h @@ -0,0 +1,1506 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_MESSAGE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_MESSAGE_H_ + +#include // uint8_t +#include +#include +#include + +#include "mysql/harness/stdx/expected.h" +#include "mysql/harness/stdx/flags.h" +#include "mysqlrouter/classic_protocol_constants.h" + +namespace classic_protocol::message::client::impl { +class BinlogDump { + public: + // flags of message::client::BinlogDump + enum class Flags : uint16_t { + non_blocking = 1 << 0, + }; +}; +} // namespace classic_protocol::message::client::impl + +namespace stdx { +// enable flag-ops for BinlogDump::Flags +template <> +struct is_flags + : std::true_type {}; +} // namespace stdx + // +namespace classic_protocol::message::client::impl { +class BinlogDumpGtid { + public: + // flags of message::client::BinlogDumpGtid + enum class Flags : uint16_t { + non_blocking = 1 << 0, + through_position = 1 << 1, + through_gtid = 1 << 2, + }; +}; +} // namespace classic_protocol::message::client::impl + +namespace stdx { +// enable flag-ops for BinlogDumpGtid::Flags +template <> +struct is_flags + : std::true_type {}; +} // namespace stdx + +namespace classic_protocol { + +namespace borrowable { +/** + * AuthMethod of classic protocol. + * + * classic proto supports negotiating the auth-method via capabilities and + * auth-method names. + */ +template +class AuthMethod { + public: + using string_type = + std::conditional_t; + + AuthMethod(classic_protocol::capabilities::value_type capabilities, + string_type auth_method_name) + : capabilities_{capabilities}, + auth_method_name_{std::move(auth_method_name)} {} + + constexpr string_type name() const { + if (auth_method_name_.empty() && + !capabilities_[classic_protocol::capabilities::pos::plugin_auth]) { + if (capabilities_ + [classic_protocol::capabilities::pos::secure_connection]) { + return "mysql_native_password"; + } else { + return "old_password"; + } + } + + return auth_method_name_; + } + + private: + const classic_protocol::capabilities::value_type capabilities_; + const string_type auth_method_name_; +}; + +namespace message { + +namespace server { + +template +class Greeting { + public: + using string_type = + std::conditional_t; + + constexpr Greeting(uint8_t protocol_version, string_type version, + uint32_t connection_id, string_type auth_method_data, + classic_protocol::capabilities::value_type capabilities, + uint8_t collation, + classic_protocol::status::value_type status_flags, + string_type auth_method_name) + : protocol_version_{protocol_version}, + version_{std::move(version)}, + connection_id_{connection_id}, + auth_method_data_{std::move(auth_method_data)}, + capabilities_{capabilities}, + collation_{collation}, + status_flags_{status_flags}, + auth_method_name_{std::move(auth_method_name)} {} + + constexpr uint8_t protocol_version() const noexcept { + return protocol_version_; + } + void protocol_version(uint8_t val) { protocol_version_ = val; } + + constexpr string_type version() const { return version_; } + void version(string_type val) { version_ = val; } + + constexpr string_type auth_method_name() const { return auth_method_name_; } + void auth_method_name(string_type val) { auth_method_name_ = val; } + constexpr string_type auth_method_data() const { return auth_method_data_; } + void auth_method_data(string_type val) { auth_method_data_ = val; } + + classic_protocol::capabilities::value_type capabilities() const noexcept { + return capabilities_; + } + + void capabilities(classic_protocol::capabilities::value_type caps) { + capabilities_ = caps; + } + + constexpr uint8_t collation() const noexcept { return collation_; } + void collation(uint8_t val) { collation_ = val; } + + constexpr classic_protocol::status::value_type status_flags() const noexcept { + return status_flags_; + } + void status_flags(classic_protocol::status::value_type val) { + status_flags_ = val; + } + + constexpr uint32_t connection_id() const noexcept { return connection_id_; } + void connection_id(uint32_t val) { connection_id_ = val; } + + private: + uint8_t protocol_version_; + string_type version_; + uint32_t connection_id_; + string_type auth_method_data_; + classic_protocol::capabilities::value_type capabilities_; + uint8_t collation_; + classic_protocol::status::value_type status_flags_; + string_type auth_method_name_; +}; + +template +inline bool operator==(const Greeting &a, + const Greeting &b) { + return (a.protocol_version() == b.protocol_version()) && + (a.version() == b.version()) && + (a.connection_id() == b.connection_id()) && + (a.auth_method_data() == b.auth_method_data()) && + (a.capabilities() == b.capabilities()) && + (a.collation() == b.collation()) && + (a.status_flags() == b.status_flags()) && + (a.auth_method_name() == b.auth_method_name()); +} + +template +class AuthMethodSwitch { + public: + using string_type = + std::conditional_t; + + constexpr AuthMethodSwitch() = default; + + constexpr AuthMethodSwitch(string_type auth_method, + string_type auth_method_data) + : auth_method_{std::move(auth_method)}, + auth_method_data_{std::move(auth_method_data)} {} + + string_type auth_method() const { return auth_method_; } + string_type auth_method_data() const { return auth_method_data_; } + + private: + string_type auth_method_; + string_type auth_method_data_; +}; + +template +inline bool operator==(const AuthMethodSwitch &a, + const AuthMethodSwitch &b) { + return (a.auth_method_data() == b.auth_method_data()) && + (a.auth_method() == b.auth_method()); +} + +/** + * Opaque auth-method-data message. + * + * used for server messages the handshake phase that aren't + * + * - Ok + * - Error + * - AuthMethodSwitch + * + * like caching_sha2_password does: + * + * - 0x01 0x02 (send public key) + * - 0x01 0x03 (send full handshake) + * - 0x01 0x04 (fast path done) + */ +template +class AuthMethodData { + public: + using string_type = + std::conditional_t; + + constexpr AuthMethodData(string_type auth_method_data) + : auth_method_data_{std::move(auth_method_data)} {} + + constexpr string_type auth_method_data() const { return auth_method_data_; } + + private: + string_type auth_method_data_; +}; + +template +inline bool operator==(const AuthMethodData &a, + const AuthMethodData &b) { + return a.auth_method_data() == b.auth_method_data(); +} + +/** + * Ok message. + * + * - affected_rows + * - last_insert_id + * - status_flags + * - warning_count + * - optional message + * - optional server-side tracked session_changes + */ +template +class Ok { + public: + using string_type = + std::conditional_t; + + constexpr Ok() = default; + + constexpr Ok(uint64_t affected_rows, uint64_t last_insert_id, + classic_protocol::status::value_type status_flags, + uint16_t warning_count, string_type message = "", + string_type session_changes = "") + : status_flags_{status_flags}, + warning_count_{warning_count}, + last_insert_id_{last_insert_id}, + affected_rows_{affected_rows}, + message_{std::move(message)}, + session_changes_{std::move(session_changes)} {} + + constexpr void status_flags(classic_protocol::status::value_type flags) { + status_flags_ = flags; + } + + constexpr classic_protocol::status::value_type status_flags() const noexcept { + return status_flags_; + } + + constexpr void warning_count(uint16_t count) { warning_count_ = count; } + constexpr uint16_t warning_count() const noexcept { return warning_count_; } + + constexpr void last_insert_id(uint64_t val) { last_insert_id_ = val; } + constexpr uint64_t last_insert_id() const noexcept { return last_insert_id_; } + + constexpr void affected_rows(uint64_t val) { affected_rows_ = val; } + constexpr uint64_t affected_rows() const noexcept { return affected_rows_; } + + constexpr void message(const string_type &msg) { message_ = msg; } + constexpr string_type message() const { return message_; } + + constexpr void session_changes(const string_type &changes) { + session_changes_ = changes; + } + /** + * get session-changes. + * + * @returns encoded array of session_track::Field + */ + string_type session_changes() const { return session_changes_; } + + private: + classic_protocol::status::value_type status_flags_{}; + uint16_t warning_count_{}; + uint64_t last_insert_id_{}; + uint64_t affected_rows_{}; + + string_type message_{}; + string_type session_changes_{}; +}; + +template +inline bool operator==(const Ok &a, const Ok &b) { + return (a.status_flags() == b.status_flags()) && + (a.warning_count() == b.warning_count()) && + (a.last_insert_id() == b.last_insert_id()) && + (a.affected_rows() == b.affected_rows()) && + (a.message() == b.message()) && + (a.session_changes() == b.session_changes()); +} + +/** + * End of Resultset message. + */ +template +class Eof : public Ok { + public: + using string_type = + std::conditional_t; + + using base__ = Ok; + + using base__::base__; + + // 3.23-like constructor + constexpr Eof() = default; + + // 4.1-like constructor + constexpr Eof(classic_protocol::status::value_type status_flags, + uint16_t warning_count) + : base__(0, 0, status_flags, warning_count) {} + + constexpr Eof(classic_protocol::status::value_type status_flags, + uint16_t warning_count, string_type message, + string_type session_changes) + : base__(0, 0, status_flags, warning_count, std::move(message), + std::move(session_changes)) {} +}; + +/** + * Error message. + */ +template +class Error { + public: + using string_type = + std::conditional_t; + + constexpr Error() = default; + /** + * construct an Error message. + * + * @param error_code error code + * @param message error message + * @param sql_state SQL state + */ + constexpr Error(uint16_t error_code, string_type message, + string_type sql_state = "HY000") + : error_code_{error_code}, + message_{std::move(message)}, + sql_state_{std::move(sql_state)} {} + + constexpr uint16_t error_code() const noexcept { return error_code_; } + constexpr void error_code(uint16_t code) { error_code_ = code; } + constexpr string_type sql_state() const { return sql_state_; } + constexpr void sql_state(const string_type &state) { sql_state_ = state; } + constexpr string_type message() const { return message_; } + constexpr void message(const string_type &msg) { message_ = msg; } + + private: + uint16_t error_code_{0}; + string_type message_; + string_type sql_state_; +}; + +template +inline bool operator==(const Error &a, const Error &b) { + return (a.error_code() == b.error_code()) && + (a.sql_state() == b.sql_state()) && (a.message() == b.message()); +} + +/** + * ColumnCount message. + */ +class ColumnCount { + public: + /** + * construct an ColumnCount message. + * + * @param count column count + */ + constexpr ColumnCount(uint64_t count) : count_{count} {} + + constexpr uint64_t count() const noexcept { return count_; } + + private: + uint64_t count_; +}; + +constexpr inline bool operator==(const ColumnCount &a, const ColumnCount &b) { + return (a.count() == b.count()); +} + +template +class ColumnMeta { + public: + using string_type = + std::conditional_t; + + constexpr ColumnMeta(string_type catalog, string_type schema, + string_type table, string_type orig_table, + string_type name, string_type orig_name, + uint16_t collation, uint32_t column_length, uint8_t type, + classic_protocol::column_def::value_type flags, + uint8_t decimals) + : catalog_{std::move(catalog)}, + schema_{std::move(schema)}, + table_{std::move(table)}, + orig_table_{std::move(orig_table)}, + name_{std::move(name)}, + orig_name_{std::move(orig_name)}, + collation_{collation}, + column_length_{column_length}, + type_{type}, + flags_{flags}, + decimals_{decimals} {} + + constexpr string_type catalog() const { return catalog_; } + constexpr string_type schema() const { return schema_; } + constexpr string_type table() const { return table_; } + constexpr string_type orig_table() const { return orig_table_; } + constexpr string_type name() const { return name_; } + constexpr string_type orig_name() const { return orig_name_; } + constexpr uint16_t collation() const { return collation_; } + constexpr uint32_t column_length() const { return column_length_; } + constexpr uint8_t type() const { return type_; } + constexpr classic_protocol::column_def::value_type flags() const { + return flags_; + } + constexpr uint8_t decimals() const { return decimals_; } + + private: + string_type catalog_; + string_type schema_; + string_type table_; + string_type orig_table_; + string_type name_; + string_type orig_name_; + uint16_t collation_; + uint32_t column_length_; + uint8_t type_; + classic_protocol::column_def::value_type flags_; + uint8_t decimals_; +}; + +template +inline bool operator==(const ColumnMeta &a, + const ColumnMeta &b) { + return (a.catalog() == b.catalog()) && (a.schema() == b.schema()) && + (a.table() == b.table()) && (a.orig_table() == b.orig_table()) && + (a.name() == b.name()) && (a.orig_name() == b.orig_name()) && + (a.collation() == b.collation()) && + (a.column_length() == b.column_length()) && (a.type() == b.type()) && + (a.flags() == b.flags()) && (a.decimals() == b.decimals()); +} + +/** + * Row in a resultset. + * + * each Row is sent as its own frame::Frame + * + * each Field in a row may either be NULL or a std::string. + */ +template +class Row { + public: + using string_type = + std::conditional_t; + + using value_type = std::optional; + using const_iterator = typename std::vector::const_iterator; + + Row(std::vector fields) : fields_{std::move(fields)} {} + + auto begin() const { return fields_.begin(); } + auto end() const { return fields_.end(); } + + private: + std::vector fields_; +}; + +template +inline bool operator==(const Row &a, const Row &b) { + auto a_iter = a.begin(); + const auto a_end = a.end(); + auto b_iter = b.begin(); + const auto b_end = b.end(); + + for (; a_iter != a_end && b_iter != b_end; ++a_iter, ++b_iter) { + if (*a_iter != *b_iter) return false; + } + + return true; +} + +template +class ResultSet { + public: + ResultSet(std::vector> column_metas, + std::vector> rows) + : column_metas_{std::move(column_metas)}, rows_{std::move(rows)} {} + + std::vector> column_metas() const { + return column_metas_; + } + std::vector> rows() const { return rows_; } + + private: + std::vector> column_metas_; + std::vector> rows_; +}; + +/** + * StmtPrepareOk message. + * + * response to a client::StmtPrepare + */ +class StmtPrepareOk { + public: + /** + * create a Ok message for a client::StmtPrepare. + * + * @param stmt_id id of the statement + * @param column_count number of columns the prepared stmt will return + * @param param_count number of parameters the prepared stmt contained + * @param warning_count number of warnings the prepared stmt created + * @param with_metadata 0 if no metadata shall be sent for "param_count" and + * "column_count". + */ + constexpr StmtPrepareOk(uint32_t stmt_id, uint16_t column_count, + uint16_t param_count, uint16_t warning_count, + uint8_t with_metadata) + : statement_id_{stmt_id}, + warning_count_{warning_count}, + param_count_{param_count}, + column_count_{column_count}, + with_metadata_{with_metadata} {} + + constexpr uint32_t statement_id() const noexcept { return statement_id_; } + constexpr void statement_id(uint32_t id) noexcept { statement_id_ = id; } + constexpr uint16_t warning_count() const noexcept { return warning_count_; } + constexpr void warning_count(uint16_t cnt) noexcept { warning_count_ = cnt; } + + constexpr uint16_t column_count() const noexcept { return column_count_; } + constexpr void column_count(uint16_t cnt) noexcept { column_count_ = cnt; } + constexpr uint16_t param_count() const noexcept { return param_count_; } + constexpr void param_count(uint16_t cnt) noexcept { param_count_ = cnt; } + constexpr uint8_t with_metadata() const noexcept { return with_metadata_; } + constexpr void with_metadata(uint8_t with) noexcept { with_metadata_ = with; } + + friend constexpr bool operator==(const StmtPrepareOk &lhs, + const StmtPrepareOk &rhs) { + return (lhs.statement_id() == rhs.statement_id()) && + (lhs.column_count() == rhs.column_count()) && + (lhs.param_count() == rhs.param_count()) && + (lhs.warning_count() == rhs.warning_count()) && + (lhs.with_metadata() == rhs.with_metadata()); + } + + private: + uint32_t statement_id_; + uint16_t warning_count_; + uint16_t param_count_; + uint16_t column_count_; + uint8_t with_metadata_; +}; + +/** + * StmtRow message. + * + * holds the same information as a Row. + * + * needs 'types' to be able to encode a Field of the Row. + */ +template +class StmtRow : public Row { + public: + using base_ = Row; + + using value_type = typename base_::value_type; + + StmtRow(std::vector types, + std::vector fields) + : base_{std::move(fields)}, types_{std::move(types)} {} + + std::vector types() const { return types_; } + + private: + std::vector types_; +}; + +template +class SendFileRequest { + public: + using string_type = + std::conditional_t; + /** + * construct a SendFileRequest message. + * + * @param filename filename + */ + constexpr SendFileRequest(string_type filename) + : filename_{std::move(filename)} {} + + constexpr string_type filename() const { return filename_; } + + private: + string_type filename_; +}; + +template +constexpr bool operator==(const SendFileRequest &a, + const SendFileRequest &b) { + return a.filename() == b.filename(); +} + +template +class Statistics { + public: + using string_type = + std::conditional_t; + + /** + * construct a Statistics message. + * + * @param stats statistics + */ + constexpr Statistics(string_type stats) : stats_{std::move(stats)} {} + + constexpr string_type stats() const { return stats_; } + + private: + string_type stats_; +}; + +template +constexpr bool operator==(const Statistics &a, + const Statistics &b) { + return a.stats() == b.stats(); +} + +} // namespace server + +namespace client { + +template +class Greeting { + public: + using string_type = + std::conditional_t; + + /** + * construct a client::Greeting message. + * + * @param capabilities protocol capabilities of the client + * @param max_packet_size max size of the frame::Frame client wants to send + * @param collation initial collation of connection + * @param username username to authenticate as + * @param auth_method_data auth-method specific data like hashed password + * @param schema initial schema of the newly authenticated session + * @param auth_method_name auth-method the data is for + * @param attributes session-attributes + */ + constexpr Greeting(classic_protocol::capabilities::value_type capabilities, + uint32_t max_packet_size, uint8_t collation, + string_type username, string_type auth_method_data, + string_type schema, string_type auth_method_name, + string_type attributes) + : capabilities_{capabilities}, + max_packet_size_{max_packet_size}, + collation_{collation}, + username_{std::move(username)}, + auth_method_data_{std::move(auth_method_data)}, + schema_{std::move(schema)}, + auth_method_name_{std::move(auth_method_name)}, + attributes_{std::move(attributes)} {} + + constexpr classic_protocol::capabilities::value_type capabilities() const { + return capabilities_; + } + + constexpr void capabilities(classic_protocol::capabilities::value_type caps) { + capabilities_ = caps; + } + + constexpr uint32_t max_packet_size() const noexcept { + return max_packet_size_; + } + constexpr void max_packet_size(uint32_t sz) noexcept { + max_packet_size_ = sz; + } + + constexpr uint8_t collation() const noexcept { return collation_; } + constexpr void collation(uint8_t coll) noexcept { collation_ = coll; } + + constexpr string_type username() const { return username_; } + constexpr void username(const string_type &v) { username_ = v; } + + constexpr string_type auth_method_data() const { return auth_method_data_; } + constexpr void auth_method_data(const string_type &v) { + auth_method_data_ = v; + } + + constexpr string_type schema() const { return schema_; } + constexpr void schema(const string_type &schema) { schema_ = schema; } + + /** + * name of the auth-method that was explicitly set. + * + * use classic_protocol::AuthMethod() to get the effective auth-method + * which may be announced though capability flags (like if + * capabilities::plugin_auth wasn't set) + */ + constexpr string_type auth_method_name() const { return auth_method_name_; } + + constexpr void auth_method_name(const string_type &name) { + auth_method_name_ = name; + } + + // [key, value]* in Codec encoding + constexpr string_type attributes() const { return attributes_; } + + constexpr void attributes(const string_type &attrs) { attributes_ = attrs; } + + private: + classic_protocol::capabilities::value_type capabilities_; + uint32_t max_packet_size_; + uint8_t collation_; + string_type username_; + string_type auth_method_data_; + string_type schema_; + string_type auth_method_name_; + string_type attributes_; +}; + +template +constexpr bool operator==(const Greeting &a, + const Greeting &b) { + return (a.capabilities() == b.capabilities()) && + (a.max_packet_size() == b.max_packet_size()) && + (a.collation() == b.collation()) && (a.username() == b.username()) && + (a.auth_method_data() == b.auth_method_data()) && + (a.schema() == b.schema()) && + (a.auth_method_name() == b.auth_method_name()) && + (a.attributes() == b.attributes()); +} + +template +class Query { + public: + using string_type = + std::conditional_t; + + struct Param { + uint16_t type_and_flags; + string_type name; + std::optional value; + + Param(uint16_t t, string_type n, std::optional v) + : type_and_flags(t), name(std::move(n)), value(std::move(v)) {} + }; + + Query(string_type statement) : statement_{std::move(statement)} {} + + /** + * construct a Query message with values. + * + * @param statement statement to query + * @param values values for statement + */ + Query(string_type statement, std::vector values) + : statement_{std::move(statement)}, values_(std::move(values)) {} + + constexpr string_type statement() const { return statement_; } + + std::vector values() const { return values_; } + + private: + string_type statement_; + std::vector values_; +}; + +template +constexpr bool operator==(const Query &a, const Query &b) { + return a.statement() == b.statement(); +} + +template +class ListFields { + public: + using string_type = + std::conditional_t; + + /** + * list columns of a table. + * + * If 'wildcard' is empty the server will execute: + * + * SHOW COLUMNS FROM table_name + * + * Otherwise: + * + * SHOW COLUMNS FROM table_name LIKE wildcard + * + * @param table_name name of table to list + * @param wildcard wildcard + */ + constexpr ListFields(string_type table_name, string_type wildcard) + : table_name_{std::move(table_name)}, wildcard_{std::move(wildcard)} {} + + constexpr string_type table_name() const { return table_name_; } + constexpr string_type wildcard() const { return wildcard_; } + + private: + string_type table_name_; + string_type wildcard_; +}; + +template +constexpr bool operator==(const ListFields &a, + const ListFields &b) { + return a.table_name() == b.table_name() && a.wildcard() == b.wildcard(); +} + +template +class InitSchema { + public: + using string_type = + std::conditional_t; + + /** + * construct a InitSchema message. + * + * @param schema schema to change to + */ + constexpr InitSchema(string_type schema) : schema_{std::move(schema)} {} + + constexpr string_type schema() const { return schema_; } + + private: + string_type schema_; +}; + +template +constexpr bool operator==(const InitSchema &a, + const InitSchema &b) { + return a.schema() == b.schema(); +} + +template +class ChangeUser { + public: + using string_type = + std::conditional_t; + /** + * construct a ChangeUser message. + * + * @param username username to change to + * @param auth_method_data auth-method specific data like hashed password + * @param schema initial schema of the newly authenticated session + * @param auth_method_name auth-method the data is for + * @param collation collation + * @param attributes session-attributes + */ + constexpr ChangeUser(string_type username, string_type auth_method_data, + string_type schema, uint16_t collation, + string_type auth_method_name, string_type attributes) + : username_{std::move(username)}, + auth_method_data_{std::move(auth_method_data)}, + schema_{std::move(schema)}, + collation_{collation}, + auth_method_name_{std::move(auth_method_name)}, + attributes_{std::move(attributes)} {} + + constexpr uint8_t collation() const noexcept { return collation_; } + constexpr string_type username() const { return username_; } + constexpr string_type auth_method_data() const { return auth_method_data_; } + constexpr string_type schema() const { return schema_; } + constexpr string_type auth_method_name() const { return auth_method_name_; } + + // [key, value]* in Codec encoding + constexpr string_type attributes() const { return attributes_; } + + private: + string_type username_; + string_type auth_method_data_; + string_type schema_; + uint16_t collation_; + string_type auth_method_name_; + string_type attributes_; +}; + +template +constexpr bool operator==(const ChangeUser &a, + const ChangeUser &b) { + return (a.collation() == b.collation()) && (a.username() == b.username()) && + (a.auth_method_data() == b.auth_method_data()) && + (a.schema() == b.schema()) && + (a.auth_method_name() == b.auth_method_name()) && + (a.attributes() == b.attributes()); +} + +// no content +class ResetConnection {}; + +constexpr bool operator==(const ResetConnection &, const ResetConnection &) { + return true; +} + +// no content +class Statistics {}; + +constexpr bool operator==(const Statistics &, const Statistics &) { + return true; +} + +class Reload { + public: + /** + * construct a Reload message. + * + * @param cmds what to reload + */ + constexpr Reload(classic_protocol::reload_cmds::value_type cmds) + : cmds_{cmds} {} + + constexpr classic_protocol::reload_cmds::value_type cmds() const { + return cmds_; + } + + private: + classic_protocol::reload_cmds::value_type cmds_; +}; + +inline bool operator==(const Reload &a, const Reload &b) { + return a.cmds() == b.cmds(); +} + +class Kill { + public: + /** + * construct a Kill message. + * + * @param connection_id payload + */ + constexpr Kill(uint32_t connection_id) : connection_id_{connection_id} {} + + constexpr uint32_t connection_id() const { return connection_id_; } + + private: + uint32_t connection_id_; +}; + +constexpr bool operator==(const Kill &a, const Kill &b) { + return a.connection_id() == b.connection_id(); +} + +template +class SendFile { + public: + using string_type = + std::conditional_t; + + /** + * construct a SendFile message. + * + * @param payload payload + */ + constexpr SendFile(string_type payload) : payload_{std::move(payload)} {} + + constexpr string_type payload() const { return payload_; } + + private: + string_type payload_; +}; + +template +inline bool operator==(const SendFile &a, + const SendFile &b) { + return a.payload() == b.payload(); +} + +template +class StmtPrepare { + public: + using string_type = + std::conditional_t; + /** + * construct a PrepareStmt message. + * + * @param statement statement to prepare + */ + constexpr StmtPrepare(string_type statement) + : statement_{std::move(statement)} {} + + constexpr string_type statement() const { return statement_; } + + private: + string_type statement_; +}; + +template +inline bool operator==(const StmtPrepare &a, + const StmtPrepare &b) { + return a.statement() == b.statement(); +} + +/** + * append data to a parameter of a prepared statement. + */ +template +class StmtParamAppendData { + public: + using string_type = + std::conditional_t; + + /** + * construct an append-data-to-parameter message. + * + * @param statement_id statement-id to close + * @param param_id parameter-id to append data to + * @param data data to append to param_id of statement_id + */ + constexpr StmtParamAppendData(uint32_t statement_id, uint16_t param_id, + string_type data) + : statement_id_{statement_id}, + param_id_{param_id}, + data_{std::move(data)} {} + + constexpr uint32_t statement_id() const { return statement_id_; } + constexpr uint16_t param_id() const { return param_id_; } + constexpr string_type data() const { return data_; } + + private: + uint32_t statement_id_; + uint16_t param_id_; + string_type data_; +}; + +template +inline bool operator==(const StmtParamAppendData &a, + const StmtParamAppendData &b) { + return a.statement_id() == b.statement_id() && a.param_id() == b.param_id() && + a.data() == b.data(); +} + +/** + * execute a prepared statement. + * + * 'values' raw bytes as encoded by the binary codec + */ +template +class StmtExecute { + public: + using string_type = + std::conditional_t; + + using value_type = std::optional; + + struct ParamDef { + ParamDef() = default; + + ParamDef(uint16_t type_and_flags_) : type_and_flags(type_and_flags_) {} + + ParamDef(uint16_t type_and_flags_, string_type name_, + bool param_already_sent_ = false) + : type_and_flags(type_and_flags_), + name(std::move(name_)), + param_already_sent(param_already_sent_) {} + + friend bool operator==(const ParamDef &lhs, const ParamDef &rhs) { + return lhs.type_and_flags == rhs.type_and_flags && lhs.name == rhs.name; + } + + uint16_t type_and_flags{}; + + string_type name; + + // param already send via stmt_param_append_data + bool param_already_sent{false}; + }; + + /** + * construct a ExecuteStmt message. + * + * @param statement_id statement id + * @param flags cursor flags + * @param iteration_count iteration_count + * @param new_params_bound new params bound + * @param types field types of the parameters + * @param values binary-encoded values without length-bytes + */ + StmtExecute(uint32_t statement_id, classic_protocol::cursor::value_type flags, + uint32_t iteration_count, bool new_params_bound, + std::vector types, std::vector values) + : statement_id_{statement_id}, + flags_{flags}, + iteration_count_{iteration_count}, + new_params_bound_{new_params_bound}, + types_{std::move(types)}, + values_{std::move(values)} {} + + uint32_t statement_id() const noexcept { return statement_id_; } + classic_protocol::cursor::value_type flags() const noexcept { return flags_; } + uint32_t iteration_count() const noexcept { return iteration_count_; } + bool new_params_bound() const noexcept { return new_params_bound_; } + std::vector types() const { return types_; } + std::vector values() const { return values_; } + + private: + uint32_t statement_id_; + classic_protocol::cursor::value_type flags_; + uint32_t iteration_count_; + bool new_params_bound_; + std::vector types_; + std::vector values_; +}; + +template +inline bool operator==(const StmtExecute &a, + const StmtExecute &b) { + return a.statement_id() == b.statement_id() && a.flags() == b.flags() && + a.iteration_count() == b.iteration_count() && + a.new_params_bound() == b.new_params_bound() && + a.types() == b.types() && a.values() == b.values(); +} + +/** + * close a prepared statement. + */ +class StmtClose { + public: + /** + * construct a StmtClose message. + * + * @param statement_id statement-id to close + */ + constexpr StmtClose(uint32_t statement_id) : statement_id_{statement_id} {} + + constexpr uint32_t statement_id() const { return statement_id_; } + + private: + uint32_t statement_id_; +}; + +constexpr bool operator==(const StmtClose &a, const StmtClose &b) { + return a.statement_id() == b.statement_id(); +} + +/** + * reset a prepared statement. + */ +class StmtReset { + public: + /** + * construct a ResetStmt message. + * + * @param statement_id statement-id to close + */ + constexpr StmtReset(uint32_t statement_id) : statement_id_{statement_id} {} + + constexpr uint32_t statement_id() const { return statement_id_; } + + private: + uint32_t statement_id_; +}; + +constexpr bool operator==(const StmtReset &a, const StmtReset &b) { + return a.statement_id() == b.statement_id(); +} + +/** + * fetch rows from an executed statement. + */ +class StmtFetch { + public: + /** + * construct a ResetStmt message. + * + * @param statement_id statement-id to close + * @param row_count statement-id to close + */ + constexpr StmtFetch(uint32_t statement_id, uint32_t row_count) + : statement_id_{statement_id}, row_count_{row_count} {} + + constexpr uint32_t statement_id() const { return statement_id_; } + constexpr uint32_t row_count() const { return row_count_; } + + private: + uint32_t statement_id_; + uint32_t row_count_; +}; + +constexpr bool operator==(const StmtFetch &a, const StmtFetch &b) { + return a.statement_id() == b.statement_id() && a.row_count() == b.row_count(); +} + +/** + * set options on the current connection. + */ +class SetOption { + public: + /** + * construct a SetOption message. + * + * @param option options to set + */ + constexpr SetOption(uint16_t option) : option_{option} {} + + constexpr uint16_t option() const { return option_; } + + private: + uint16_t option_; +}; + +constexpr bool operator==(const SetOption &a, const SetOption &b) { + return a.option() == b.option(); +} + +// no content +class Quit {}; + +constexpr bool operator==(const Quit &, const Quit &) { return true; } + +// no content +class Ping {}; + +constexpr bool operator==(const Ping &, const Ping &) { return true; } + +// no content +class Debug {}; + +constexpr bool operator==(const Debug &, const Debug &) { return true; } + +template +class AuthMethodData { + public: + using string_type = + std::conditional_t; + + /** + * send data for the current auth-method to server. + * + * @param auth_method_data data of the auth-method + */ + constexpr AuthMethodData(string_type auth_method_data) + : auth_method_data_{std::move(auth_method_data)} {} + + constexpr string_type auth_method_data() const { return auth_method_data_; } + + private: + string_type auth_method_data_; +}; + +template +constexpr bool operator==(const AuthMethodData &a, + const AuthMethodData &b) { + return a.auth_method_data() == b.auth_method_data(); +} + +// switch to Clone Protocol. +// +// response: server::Ok -> clone protocol +// response: server::Error +// +// no content +class Clone {}; + +template +class BinlogDump { + public: + using string_type = + std::conditional_t; + + using Flags = + typename classic_protocol::message::client::impl::BinlogDump::Flags; + + constexpr BinlogDump(stdx::flags flags, uint32_t server_id, + string_type filename, uint32_t position) + : position_{position}, + flags_{flags}, + server_id_{server_id}, + filename_{std::move(filename)} {} + + [[nodiscard]] constexpr stdx::flags flags() const { return flags_; } + [[nodiscard]] constexpr uint32_t server_id() const { return server_id_; } + [[nodiscard]] constexpr string_type filename() const { return filename_; } + [[nodiscard]] constexpr uint64_t position() const { return position_; } + + private: + uint32_t position_; + stdx::flags flags_; + uint32_t server_id_; + string_type filename_; +}; + +template +class BinlogDumpGtid { + public: + using string_type = + std::conditional_t; + + using Flags = + typename classic_protocol::message::client::impl::BinlogDumpGtid::Flags; + + constexpr BinlogDumpGtid(stdx::flags flags, uint32_t server_id, + string_type filename, uint64_t position, + string_type sids) + : flags_{flags}, + server_id_{server_id}, + filename_{std::move(filename)}, + position_{position}, + sids_{std::move(sids)} {} + + [[nodiscard]] constexpr stdx::flags flags() const { return flags_; } + [[nodiscard]] constexpr uint32_t server_id() const { return server_id_; } + [[nodiscard]] constexpr string_type filename() const { return filename_; } + [[nodiscard]] constexpr uint64_t position() const { return position_; } + [[nodiscard]] constexpr string_type sids() const { return sids_; } + + private: + stdx::flags flags_; + uint32_t server_id_; + string_type filename_; + uint64_t position_; + string_type sids_; +}; + +template +class RegisterReplica { + public: + using string_type = + std::conditional_t; + + constexpr RegisterReplica(uint32_t server_id, string_type hostname, + string_type username, string_type password, + uint16_t port, uint32_t replication_rank, + uint32_t master_id) + : server_id_{server_id}, + hostname_{std::move(hostname)}, + username_{std::move(username)}, + password_{std::move(password)}, + port_{port}, + replication_rank_{replication_rank}, + master_id_{master_id} {} + + [[nodiscard]] constexpr uint32_t server_id() const { return server_id_; } + [[nodiscard]] constexpr string_type hostname() const { return hostname_; } + [[nodiscard]] constexpr string_type username() const { return username_; } + [[nodiscard]] constexpr string_type password() const { return password_; } + [[nodiscard]] constexpr uint16_t port() const { return port_; } + [[nodiscard]] constexpr uint32_t replication_rank() const { + return replication_rank_; + } + [[nodiscard]] constexpr uint32_t master_id() const { return master_id_; } + + private: + uint32_t server_id_; + string_type hostname_; + string_type username_; + string_type password_; + uint16_t port_; + uint32_t replication_rank_; + uint32_t master_id_; +}; + +} // namespace client +} // namespace message +} // namespace borrowable + +namespace message { +namespace server { +using Ok = borrowable::message::server::Ok; +using Error = borrowable::message::server::Error; +using Eof = borrowable::message::server::Eof; +using Greeting = borrowable::message::server::Greeting; +using ColumnCount = borrowable::message::server::ColumnCount; +using ColumnMeta = borrowable::message::server::ColumnMeta; +using AuthMethodSwitch = borrowable::message::server::AuthMethodSwitch; +using AuthMethodData = borrowable::message::server::AuthMethodData; +using SendFileRequest = borrowable::message::server::SendFileRequest; +using Row = borrowable::message::server::Row; +using StmtRow = borrowable::message::server::StmtRow; +using StmtPrepareOk = borrowable::message::server::StmtPrepareOk; +using Statistics = borrowable::message::server::Statistics; +} // namespace server + +namespace client { +using Greeting = borrowable::message::client::Greeting; +using AuthMethodData = borrowable::message::client::AuthMethodData; +using InitSchema = borrowable::message::client::InitSchema; +using ListFields = borrowable::message::client::ListFields; +using Query = borrowable::message::client::Query; +using RegisterReplica = borrowable::message::client::RegisterReplica; +using Ping = borrowable::message::client::Ping; +using Debug = borrowable::message::client::Debug; +using Kill = borrowable::message::client::Kill; +using ChangeUser = borrowable::message::client::ChangeUser; +using Reload = borrowable::message::client::Reload; +using ResetConnection = borrowable::message::client::ResetConnection; +using Quit = borrowable::message::client::Quit; +using StmtPrepare = borrowable::message::client::StmtPrepare; +using StmtExecute = borrowable::message::client::StmtExecute; +using StmtReset = borrowable::message::client::StmtReset; +using StmtClose = borrowable::message::client::StmtClose; +using StmtParamAppendData = + borrowable::message::client::StmtParamAppendData; +using SetOption = borrowable::message::client::SetOption; +using StmtFetch = borrowable::message::client::StmtFetch; +using Statistics = borrowable::message::client::Statistics; +using SendFile = borrowable::message::client::SendFile; +using Clone = borrowable::message::client::Clone; +using BinlogDump = borrowable::message::client::BinlogDump; +using BinlogDumpGtid = borrowable::message::client::BinlogDumpGtid; +} // namespace client +} // namespace message + +namespace borrowed { +namespace message { +namespace server { +using Ok = borrowable::message::server::Ok; +using Error = borrowable::message::server::Error; +using Eof = borrowable::message::server::Eof; +using Greeting = borrowable::message::server::Greeting; +using ColumnCount = borrowable::message::server::ColumnCount; +using ColumnMeta = borrowable::message::server::ColumnMeta; +using AuthMethodSwitch = borrowable::message::server::AuthMethodSwitch; +using AuthMethodData = borrowable::message::server::AuthMethodData; +using SendFileRequest = borrowable::message::server::SendFileRequest; +using Row = borrowable::message::server::Row; +using StmtRow = borrowable::message::server::StmtRow; +using StmtPrepareOk = borrowable::message::server::StmtPrepareOk; +using Statistics = borrowable::message::server::Statistics; +} // namespace server + +namespace client { +using Greeting = borrowable::message::client::Greeting; +using AuthMethodData = borrowable::message::client::AuthMethodData; +using Query = borrowable::message::client::Query; +using InitSchema = borrowable::message::client::InitSchema; +using ListFields = borrowable::message::client::ListFields; +using RegisterReplica = borrowable::message::client::RegisterReplica; +using Ping = borrowable::message::client::Ping; +using Debug = borrowable::message::client::Debug; +using Kill = borrowable::message::client::Kill; +using ChangeUser = borrowable::message::client::ChangeUser; +using Reload = borrowable::message::client::Reload; +using ResetConnection = borrowable::message::client::ResetConnection; +using Quit = borrowable::message::client::Quit; +using StmtPrepare = borrowable::message::client::StmtPrepare; +using StmtExecute = borrowable::message::client::StmtExecute; +using StmtReset = borrowable::message::client::StmtReset; +using StmtClose = borrowable::message::client::StmtClose; +using StmtParamAppendData = + borrowable::message::client::StmtParamAppendData; +using SetOption = borrowable::message::client::SetOption; +using StmtFetch = borrowable::message::client::StmtFetch; +using Statistics = borrowable::message::client::Statistics; +using SendFile = borrowable::message::client::SendFile; +using Clone = borrowable::message::client::Clone; +using BinlogDump = borrowable::message::client::BinlogDump; +using BinlogDumpGtid = borrowable::message::client::BinlogDumpGtid; +} // namespace client +} // namespace message +} // namespace borrowed + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_session_track.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_session_track.h new file mode 100644 index 0000000..c516149 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_session_track.h @@ -0,0 +1,307 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_SESSION_TRACK_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_SESSION_TRACK_H_ + +// session_track as used by message::server::Ok and message::server::Eof + +#include +#include + +namespace classic_protocol { + +namespace borrowable { +namespace session_track { + +/** + * Field of a session-track array. + * + * used in server::Ok and server::Eof + */ +template +class Field { + public: + using string_type = + std::conditional_t; + + constexpr Field(uint8_t type, string_type data) + : type_{type}, data_{std::move(data)} {} + + constexpr uint8_t type() const noexcept { return type_; } + constexpr string_type data() const noexcept { return data_; } + + private: + uint8_t type_; + string_type data_; +}; + +template +inline bool operator==(const Field &a, const Field &b) { + return (a.type() == b.type()) && (a.data() == b.data()); +} + +/** + * system-variable changed. + * + * see: session_track_system_variable + */ +template +class SystemVariable { + public: + using string_type = + std::conditional_t; + constexpr SystemVariable(string_type key, string_type value) + : key_{std::move(key)}, value_{std::move(value)} {} + + constexpr string_type key() const noexcept { return key_; } + constexpr string_type value() const noexcept { return value_; } + + private: + string_type key_; + string_type value_; +}; + +template +inline bool operator==(const SystemVariable &a, + const SystemVariable &b) { + return (a.key() == b.key()) && (a.value() == b.value()); +} + +/** + * schema changed. + * + * see: session_track_schema + */ +template +class Schema { + public: + using string_type = + std::conditional_t; + + constexpr Schema(string_type schema) : schema_{std::move(schema)} {} + + constexpr string_type schema() const noexcept { return schema_; } + + private: + string_type schema_; +}; + +template +inline bool operator==(const Schema &a, const Schema &b) { + return (a.schema() == b.schema()); +} + +/** + * state changed. + * + * see: session_track_session_state + */ +class State { + public: + constexpr State(int8_t state) : state_{std::move(state)} {} + + constexpr int8_t state() const noexcept { return state_; } + + private: + int8_t state_; +}; + +constexpr inline bool operator==(const State &a, const State &b) { + return (a.state() == b.state()); +} + +/** + * gtid changed. + * + * - FixedInt<1> spec + * - gtid-string + * - + * + * see: session_track_gtid + */ +template +class Gtid { + public: + using string_type = + std::conditional_t; + constexpr Gtid(uint8_t spec, string_type gtid) + : spec_{spec}, gtid_{std::move(gtid)} {} + + constexpr uint8_t spec() const noexcept { return spec_; } + constexpr string_type gtid() const { return gtid_; } + + private: + uint8_t spec_; + string_type gtid_; +}; + +template +inline bool operator==(const Gtid &a, const Gtid &b) { + return (a.spec() == b.spec()) && (a.gtid() == b.gtid()); +} + +/** + * TransactionState changed. + * + * - trx_type: Explicit|Implicit|none + * - read_unsafe: one_or_more|none + * - read_trx: one_or_more|none + * - write_unsafe: one_or_more|none + * - write_trx: one_or_more|none + * - stmt_unsafe: one_or_more|none + * - resultset: one_or_more|none + * - locked_tables: one_or_more|none + * + * implicit transaction: no autocommit, stmt against transactionable table + * without START TRANSACTION + * explicit transaction: START TRANSACTION + * + * read_unsafe: read-operation against non-transactionable table + * read_trx: read-operation against transactionable table + * write_unsafe: write-operation against non-transactionable table + * write_trx: write-operation against transactionable table + * stmt_unsafe: an unusafe statement was executed like RAND() + * resultset: some resultset was sent + * locked_tables: some tables got locked explicitly + * + * 'resultset' may be triggered without 'read_trx' and 'read_unsafe' if a + * 'SELECT' was executed against 'dual' or without table. + * + * see: session_track_transaction_info + */ +class TransactionState { + public: + constexpr TransactionState(char trx_type, char read_unsafe, char read_trx, + char write_unsafe, char write_trx, + char stmt_unsafe, char resultset, + char locked_tables) + : trx_type_{trx_type}, + read_unsafe_{read_unsafe}, + read_trx_{read_trx}, + write_unsafe_{write_unsafe}, + write_trx_{write_trx}, + stmt_unsafe_{stmt_unsafe}, + resultset_{resultset}, + locked_tables_{locked_tables} {} + + constexpr TransactionState(std::span val) + : trx_type_{val[0]}, + read_unsafe_{val[1]}, + read_trx_{val[2]}, + write_unsafe_{val[3]}, + write_trx_{val[4]}, + stmt_unsafe_{val[5]}, + resultset_{val[6]}, + locked_tables_{val[7]} {} + + constexpr char trx_type() const noexcept { return trx_type_; } + constexpr char read_unsafe() const noexcept { return read_unsafe_; } + constexpr char read_trx() const noexcept { return read_trx_; } + constexpr char write_unsafe() const noexcept { return write_unsafe_; } + constexpr char write_trx() const noexcept { return write_trx_; } + constexpr char stmt_unsafe() const noexcept { return stmt_unsafe_; } + constexpr char resultset() const noexcept { return resultset_; } + constexpr char locked_tables() const noexcept { return locked_tables_; } + + private: + char trx_type_; // T|I|_ + char read_unsafe_; // r|_ + char read_trx_; // R|_ + char write_unsafe_; // w|_ + char write_trx_; // W|_ + char stmt_unsafe_; // s|_ + char resultset_; // S|_ + char locked_tables_; // L|_ +}; + +inline bool operator==(const TransactionState &a, const TransactionState &b) { + return (a.trx_type() == b.trx_type()) && + (a.read_unsafe() == b.read_unsafe()) && + (a.read_trx() == b.read_trx()) && + (a.write_unsafe() == b.write_unsafe()) && + (a.write_trx() == b.write_trx()) && + (a.stmt_unsafe() == b.stmt_unsafe()) && + (a.resultset() == b.resultset()) && + (a.locked_tables() == b.locked_tables()); +} + +/** + * TransactionCharacteristics changed. + * + * resembles the SQL-text which started the transaction. + * + * see: session_track_transaction_info + */ +template +class TransactionCharacteristics { + public: + using string_type = + std::conditional_t; + + constexpr TransactionCharacteristics(string_type characteristics) + : characteristics_{std::move(characteristics)} {} + + constexpr string_type characteristics() const { return characteristics_; } + + private: + string_type characteristics_; +}; + +template +inline bool operator==(const TransactionCharacteristics &a, + const TransactionCharacteristics &b) { + return (a.characteristics() == b.characteristics()); +} +} // namespace session_track +} // namespace borrowable + +namespace borrowed { +namespace session_track { +using Field = borrowable::session_track::Field; +using TransactionCharacteristics = + borrowable::session_track::TransactionCharacteristics; +using TransactionState = borrowable::session_track::TransactionState; +using SystemVariable = borrowable::session_track::SystemVariable; +using Schema = borrowable::session_track::Schema; +using State = borrowable::session_track::State; +using Gtid = borrowable::session_track::Gtid; +} // namespace session_track +} // namespace borrowed + +namespace session_track { +using Field = borrowable::session_track::Field; +using TransactionCharacteristics = + borrowable::session_track::TransactionCharacteristics; +using TransactionState = borrowable::session_track::TransactionState; +using SystemVariable = borrowable::session_track::SystemVariable; +using Schema = borrowable::session_track::Schema; +using State = borrowable::session_track::State; +using Gtid = borrowable::session_track::Gtid; +} // namespace session_track + +} // namespace classic_protocol + +#endif diff --git a/extra/mysql_protocol/mysqlrouter/classic_protocol_wire.h b/extra/mysql_protocol/mysqlrouter/classic_protocol_wire.h new file mode 100644 index 0000000..858d103 --- /dev/null +++ b/extra/mysql_protocol/mysqlrouter/classic_protocol_wire.h @@ -0,0 +1,167 @@ +/* + Copyright (c) 2019, 2026, Oracle 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 designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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 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 MYSQL_ROUTER_CLASSIC_PROTOCOL_WIRE_H_ +#define MYSQL_ROUTER_CLASSIC_PROTOCOL_WIRE_H_ + +#include +#include + +namespace classic_protocol { + +namespace borrowable { +namespace wire { + +template +class String { + public: + using value_type = + std::conditional_t; + + constexpr String() = default; + constexpr String(value_type str) : str_{std::move(str)} {} + + constexpr value_type value() const { return str_; } + + private: + value_type str_; +}; + +template +inline bool operator==(const String &lhs, + const String &rhs) { + return lhs.value() == rhs.value(); +} + +template +class NulTermString : public String { + public: + using String::String; +}; + +template +class VarString : public String { + public: + using String::String; +}; + +// basic POD types of the mysql classic-protocol's wire encoding: +// +// - fixed size integers +// - variable sized integers +// - fixed size strings +// - variable sized strings +// - nul-terminated strings +// - NULL + +template +class BasicInt { + public: + using value_type = U; + + constexpr BasicInt(value_type val) : val_{val} {} + + constexpr value_type value() const { return val_; } + + private: + value_type val_; +}; + +template +constexpr bool operator==(const BasicInt &lhs, const BasicInt &rhs) { + return lhs.value() == rhs.value(); +} + +class VarInt : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +template +class FixedInt; + +template <> +class FixedInt<1> : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +template <> +class FixedInt<2> : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +template <> +class FixedInt<3> : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +template <> +class FixedInt<4> : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +template <> +class FixedInt<8> : public BasicInt { + public: + using BasicInt::BasicInt; +}; + +class Null {}; +} // namespace wire +} // namespace borrowable + +namespace borrowed { +namespace wire { + +using String = borrowable::wire::String; +using NulTermString = borrowable::wire::NulTermString; +using VarString = borrowable::wire::VarString; +using Null = borrowable::wire::Null; +template +using FixedInt = borrowable::wire::FixedInt; +using VarInt = borrowable::wire::VarInt; +} // namespace wire +} // namespace borrowed + +namespace wire { + +using String = borrowable::wire::String; +using NulTermString = borrowable::wire::NulTermString; +using VarString = borrowable::wire::VarString; +using Null = borrowable::wire::Null; + +template +using FixedInt = borrowable::wire::FixedInt; + +using VarInt = borrowable::wire::VarInt; +} // namespace wire + +} // namespace classic_protocol + +#endif diff --git a/src/minimysql/caching_sha2_password_authenticator.cpp b/src/minimysql/caching_sha2_password_authenticator.cpp new file mode 100644 index 0000000..bc7ae81 --- /dev/null +++ b/src/minimysql/caching_sha2_password_authenticator.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2023-2026 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 "minimysql/caching_sha2_password_authenticator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +enum class digest_code_type : std::uint8_t { + sha256, +}; + +class digest_context { +public: + // no std::string_view for 'type' as we need it to be nul-terminated + explicit digest_context(digest_code_type digest_code) + : impl_{EVP_MD_CTX_new(), digest_context_deleter{}} { + if (!impl_) { + throw std::runtime_error{"failed to create digest context"}; + } + if (EVP_DigestInit_ex(impl_.get(), get_md_by_digest_code(digest_code), + nullptr) == 0) { + throw std::runtime_error{"failed to initialize digest context"}; + } + } + + ~digest_context() noexcept = default; + + digest_context(const digest_context &obj) = delete; + digest_context(digest_context &&obj) noexcept = delete; + + digest_context &operator=(const digest_context &obj) = delete; + digest_context &operator=(digest_context &&obj) noexcept = delete; + + [[nodiscard]] std::size_t get_size_in_bytes() const noexcept { + assert(impl_); + auto native_result{EVP_MD_CTX_size(impl_.get())}; + assert(native_result != -1); + return static_cast(native_result); + } + + void update(std::string_view data) { + assert(impl_); + if (EVP_DigestUpdate(impl_.get(), std::data(data), std::size(data)) == 0) { + throw std::runtime_error{"failed to update digest context"}; + } + } + std::string finalize() { + assert(impl_); + std::string result(get_size_in_bytes(), '\0'); + + unsigned int result_size = 0; + if (EVP_DigestFinal_ex( + impl_.get(), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(std::data(result)), + &result_size) == 0) { + throw std::runtime_error{"cannot finalize digest context"}; + } + assert(result_size == std::size(result)); + + impl_.reset(); + return result; + } + +private: + struct digest_context_deleter { + void operator()(EVP_MD_CTX *digest_context) const noexcept { + // null-ness is handled by EVP_MD_CTX_free + EVP_MD_CTX_free(digest_context); + } + }; + + using impl_ptr = std::unique_ptr; + impl_ptr impl_; + + [[nodiscard]] static const EVP_MD * + get_md_by_digest_code(digest_code_type digest_code) noexcept { + switch (digest_code) { + case digest_code_type::sha256: + return EVP_sha256(); + default: + // should never happen as we only construct digest_context with supported + // digest_code_type + return nullptr; + } + } +}; + +std::string calculate_digest(digest_code_type digest_code, + std::string_view data) { + digest_context ctx(digest_code); + ctx.update(data); + return ctx.finalize(); +} + +} // anonymous namespace + +namespace minimysql { + +std::string caching_sha2_password_authenticator::scramble( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string_view password, std::string_view salt) { + // this is how client calculates client_auth_data for caching_sha2_password + // plugin: SHA256(password) XOR SHA256(SHA256(SHA256(password)), + // server_auth_data) + + // server, provided that it knows original password and server_auth_data + // (salt), can verify client_auth_data by calculating the same way and + // comparing the result with client_auth_data + const auto digest_code{digest_code_type::sha256}; + + // calculating hashed password + auto result{calculate_digest(digest_code, password)}; + + // calculating double-hashed password + const auto double_hashed_password{calculate_digest(digest_code, result)}; + + // calculating salted triple-hashed password + digest_context ctx(digest_code); + ctx.update(double_hashed_password); + ctx.update(salt); + const auto salted_triple_hashed_password{ctx.finalize()}; + + assert(std::size(result) == std::size(salted_triple_hashed_password)); + std::ranges::transform(result, salted_triple_hashed_password, + std::begin(result), std::bit_xor{}); + return result; +} + +} // namespace minimysql diff --git a/src/minimysql/caching_sha2_password_authenticator.hpp b/src/minimysql/caching_sha2_password_authenticator.hpp new file mode 100644 index 0000000..b9f69bb --- /dev/null +++ b/src/minimysql/caching_sha2_password_authenticator.hpp @@ -0,0 +1,33 @@ +// Copyright (c) 2023-2026 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 MINIMYSQL_CACHING_SHA2_PASSWORD_AUTHENTICATOR_HPP +#define MINIMYSQL_CACHING_SHA2_PASSWORD_AUTHENTICATOR_HPP + +#include +#include + +namespace minimysql { + +class caching_sha2_password_authenticator { +public: + static constexpr std::string_view plugin_name{"caching_sha2_password"}; + + static std::string scramble(std::string_view password, std::string_view salt); +}; + +} // namespace minimysql + +#endif // MINIMYSQL_CACHING_SHA2_PASSWORD_AUTHENTICATOR_HPP diff --git a/src/minimysql/connection_context.cpp b/src/minimysql/connection_context.cpp new file mode 100644 index 0000000..29bdf39 --- /dev/null +++ b/src/minimysql/connection_context.cpp @@ -0,0 +1,642 @@ +// Copyright (c) 2023-2026 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 "minimysql/connection_context.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +// code included from MySQL Router's classic protocol codec implementation +// has a number of conversion warnings, so we disable them before inclusion + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsign-conversion" +#pragma GCC diagnostic ignored "-Wconversion" + +#include "mysqlrouter/classic_protocol_codec_base.h" +#include "mysqlrouter/classic_protocol_codec_frame.h" +#include "mysqlrouter/classic_protocol_codec_message.h" +#include "mysqlrouter/classic_protocol_constants.h" +#include "mysqlrouter/classic_protocol_frame.h" +#include "mysqlrouter/classic_protocol_message.h" +#include "mysqlrouter/classic_protocol_wire.h" + +#include "mysql/harness/stdx/flags.h" + +#pragma GCC diagnostic pop + +#include "minimysql/caching_sha2_password_authenticator.hpp" + +namespace minimysql { + +namespace { + +template +classic_protocol::frame::Frame +decode_client_command_frame(const connection_buffer_type &payload, + const capability_bitset &shared_capabilities) { + auto buffer{boost::asio::buffer(payload)}; + using frame_type = classic_protocol::frame::Frame; + + auto decode_result{ + classic_protocol::decode(buffer, shared_capabilities)}; + if (!decode_result) { + throw boost::system::system_error{decode_result.error()}; + } + + return decode_result.value().second; +} + +} // namespace + +connection_context::connection_context( + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string_view server_username, std::string_view server_password) + : server_username_(server_username), server_password_(server_password), + connection_id_(next_connection_id_++) { + static_assert(std::is_same_v, + "capability_bitset MUST be the same type as " + "classic_protocol::capabilities::value_type"); +} + +[[nodiscard]] bool connection_context::check_client_authentication() const { + return get_client_username() == get_server_username() && + get_client_auth_method_data() == + caching_sha2_password_authenticator::scramble( + get_server_password(), get_server_auth_method_data()); +} + +[[nodiscard]] bool +connection_context::check_shared_plugin_auth_supported() const { + // this method is not noexcept as it used std::bitset<>::test() which is + // not noexcept + return get_shared_capabilities().test( + classic_protocol::capabilities::pos::plugin_auth); +} + +[[nodiscard]] bool +connection_context::check_shared_text_result_with_session_tracking_supported() + const { + // this method is not noexcept as it used std::bitset<>::test() which is + // not noexcept + return get_shared_capabilities().test( + classic_protocol::capabilities::pos::text_result_with_session_tracking); +} + +[[nodiscard]] bool +connection_context::check_binlog_non_blocking_dump() const noexcept { + stdx::flags + binlog_dump_flags{}; + binlog_dump_flags.underlying_value(get_binlog_flags()); + return (binlog_dump_flags & + classic_protocol::message::client::BinlogDump::Flags::non_blocking); +} + +[[nodiscard]] std::size_t +connection_context::get_frame_header_length() noexcept { + return classic_protocol::Codec::max_size(); +} + +[[nodiscard]] std::size_t +connection_context::parse_frame_header(const connection_buffer_type &payload) { + + auto buffer{boost::asio::buffer(payload)}; + auto decode_result{ + classic_protocol::decode(buffer, {})}; + if (!decode_result) { + throw boost::system::system_error{decode_result.error()}; + } + + return decode_result.value().second.payload_size(); +} + +[[nodiscard]] std::string +connection_context::generate_encoded_server_greeting() { + std::string result_buffer{}; + + server_capabilities_ = get_default_server_capabilities(); + server_auth_method_ = std::string{default_server_auth_method}; + + // for historical reasons sever auth data must include a trailing '\0' byte + const std::string fixed_server_auth_data{generate_server_auth_method_data() + + '\0'}; + + const classic_protocol::message::server::Greeting server_greeting{ + default_server_protocol_version, + std::string{default_server_version}, + get_connection_id(), + fixed_server_auth_data, + get_server_capabilities(), + default_server_collation, + default_server_status_flags, + get_server_auth_method()}; + + using server_greeting_frame = classic_protocol::frame::Frame< + classic_protocol::message::server::Greeting>; + auto encode_result{classic_protocol::encode( + {generate_sequence_number(), server_greeting}, get_server_capabilities(), + boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + return result_buffer; +} + +void connection_context::parse_client_greeting( + const connection_buffer_type &payload) { + auto buffer{boost::asio::buffer(payload)}; + using client_greeting_frame = classic_protocol::frame::Frame< + classic_protocol::message::client::Greeting>; + auto decode_result{classic_protocol::decode( + buffer, get_server_capabilities())}; + if (!decode_result) { + throw boost::system::system_error{decode_result.error()}; + } + + validate_and_update_sequence_number(decode_result.value().second.seq_id()); + + const auto &client_greeting{decode_result.value().second.payload()}; + + client_capabilities_ = client_greeting.capabilities(); + client_auth_method_ = client_greeting.auth_method_name(); + client_auth_method_data_ = client_greeting.auth_method_data(); + client_username_ = client_greeting.username(); + client_schema_ = client_greeting.schema(); + client_collation_ = client_greeting.collation(); + client_max_packet_size_ = client_greeting.max_packet_size(); + client_attributes_ = client_greeting.attributes(); +} + +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_fast_auth() { + std::string result_buffer{}; + + static constexpr std::string_view fast_auth_code{ + "\x03"}; // 0x03 means "fast auth success" in caching_sha2_password + // protocol + using auth_method_data_frame = classic_protocol::frame::Frame< + classic_protocol::message::server::AuthMethodData>; + auto encode_res = classic_protocol::encode( + {generate_sequence_number(), {std::string{fast_auth_code}}}, + get_shared_capabilities(), boost::asio::dynamic_buffer(result_buffer)); + + if (!encode_res) { + throw boost::system::system_error{encode_res.error()}; + } + return result_buffer; +} + +[[nodiscard]] connection_buffer_type connection_context::generate_encoded_ok() { + std::string result_buffer{}; + + const classic_protocol::message::server::Ok ok_message{}; + + using ok_frame = + classic_protocol::frame::Frame; + auto encode_result{classic_protocol::encode( + {generate_sequence_number(), ok_message}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + return result_buffer; +} + +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_eof() { + std::string result_buffer{}; + + const classic_protocol::message::server::Eof eof_message{}; + + using eof_frame = + classic_protocol::frame::Frame; + auto encode_result{classic_protocol::encode( + {generate_sequence_number(), eof_message}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + return result_buffer; +} + +[[nodiscard]] connection_buffer_type connection_context::generate_encoded_error( + std::uint16_t error_code, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + std::string_view message, std::string_view sql_state) { + std::string result_buffer{}; + + classic_protocol::message::server::Error error_message{}; + error_message.error_code(error_code); + error_message.message(std::string{message}); + error_message.sql_state(std::string{sql_state}); + + using error_frame = + classic_protocol::frame::Frame; + auto encode_result{classic_protocol::encode( + {generate_sequence_number(), error_message}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + return result_buffer; +} + +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_access_denied() { + return generate_encoded_error( + ER_ACCESS_DENIED_ERROR, + "Access Denied for user '" + get_client_username() + "'@'%'", "28000"); +} +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_unknown_command() { + return generate_encoded_error(ER_UNKNOWN_COM_ERROR, "Unknown command", + "HY000"); +} +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_syntax_error() { + // 00000 Success + // 01000 Warning + // 02000 No data + // 23000 Integrity constraint violation + // 28000 Invalid authorization + // 42000 Syntax error or access rule violation + // HY000 General error (MySQL‑specific catch‑all) + return generate_encoded_error(ER_PARSE_ERROR, "Syntax error", "42000"); +} + +[[nodiscard]] connection_buffer_type +connection_context::generate_encoded_binlog_event(std::string_view event_data) { + std::string result_buffer{}; + result_buffer.reserve(get_frame_header_length() + std::size(event_data) + 1U); + + auto encode_result{classic_protocol::encode( + {std::size(event_data) + 1U, generate_sequence_number()}, + get_shared_capabilities(), boost::asio::dynamic_buffer(result_buffer))}; + + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } + result_buffer += + '\0'; // the first byte in the payload must be '\0' to indicate OK + result_buffer += event_data; + return result_buffer; +} + +void connection_context::parse_client_command( + const connection_buffer_type &payload) { + if (payload.size() <= get_frame_header_length()) { + throw boost::system::system_error{ + make_error_code(std::errc::protocol_error)}; + } + + const auto command_byte_index{get_frame_header_length()}; + const auto command_byte{ + static_cast(payload[command_byte_index])}; + + switch (command_byte) { + case classic_protocol::Codec< + classic_protocol::message::client::Query>::cmd_byte(): { + auto frame = + decode_client_command_frame( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::query; + client_statement_ = frame.payload().statement(); + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::Quit>::cmd_byte(): { + auto frame = + decode_client_command_frame( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::quit; + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::ResetConnection>::cmd_byte(): { + auto frame = decode_client_command_frame< + classic_protocol::message::client::ResetConnection>( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::reset_connection; + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::ChangeUser>::cmd_byte(): { + auto frame = decode_client_command_frame< + classic_protocol::message::client::ChangeUser>( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::change_user; + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::Ping>::cmd_byte(): { + auto frame = + decode_client_command_frame( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::ping; + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::BinlogDump>::cmd_byte(): { + auto frame = decode_client_command_frame< + classic_protocol::message::client::BinlogDump>( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::binlog_dump; + const auto &binlog_dump_payload{frame.payload()}; + binlog_flags_ = binlog_dump_payload.flags().underlying_value(); + binlog_server_id_ = binlog_dump_payload.server_id(); + binlog_filename_ = binlog_dump_payload.filename(); + binlog_position_ = binlog_dump_payload.position(); + break; + } + case classic_protocol::Codec< + classic_protocol::message::client::BinlogDumpGtid>::cmd_byte(): { + auto frame = decode_client_command_frame< + classic_protocol::message::client::BinlogDumpGtid>( + payload, get_shared_capabilities()); + validate_and_update_sequence_number(frame.seq_id()); + + client_command_ = client_command_type::binlog_dump_gtid; + break; + } + default: + client_command_ = client_command_type::unknown; + } +} + +std::atomic_uint32_t connection_context::next_connection_id_{0U}; + +[[nodiscard]] capability_bitset +connection_context::get_default_server_capabilities() noexcept { + return classic_protocol::capabilities::long_password | + classic_protocol::capabilities::found_rows | + classic_protocol::capabilities::long_flag | + classic_protocol::capabilities::connect_with_schema | + classic_protocol::capabilities::no_schema | + // compress (not yet) + classic_protocol::capabilities::odbc | + classic_protocol::capabilities::local_files | + // ignore_space (client only) + classic_protocol::capabilities::protocol_41 | + // interactive (client-only) + // ssl (below) + // ignore sigpipe (client-only) + classic_protocol::capabilities::transactions | + classic_protocol::capabilities::secure_connection | + // multi_statements (not yet) + classic_protocol::capabilities::multi_results | + classic_protocol::capabilities::ps_multi_results | + classic_protocol::capabilities::plugin_auth | + classic_protocol::capabilities::connect_attributes | + classic_protocol::capabilities::client_auth_method_data_varint + // disable expired_passwords + // disable session_track + // disable text_result_with_session_tracking + // optional_resultset_metadata (not yet) + // compress_zstd (not yet) + ; +} + +[[nodiscard]] const std::string & +connection_context::generate_server_auth_method_data() { + // TODO: generate random auth method data (for caching_sha2_password, it must + // be 20 random bytes) + server_auth_method_data_ = "01234567890123456789"; + return server_auth_method_data_; +} + +[[nodiscard]] std::uint8_t connection_context::generate_sequence_number() { + return sequence_number_++; +} + +void connection_context::validate_and_update_sequence_number( + std::uint8_t sequence_number) { + if (sequence_number != sequence_number_) { + throw boost::system::system_error{ + make_error_code(std::errc::protocol_error)}; + } + ++sequence_number_; +} + +void connection_context::encode_resultset_number_of_columns_internal( + connection_buffer_container &result_buffers, + std::size_t number_of_columns) { + const classic_protocol::wire::VarInt column_count{ + static_cast(number_of_columns)}; + using column_count_frame = + classic_protocol::frame::Frame; + const auto encode_result = classic_protocol::encode( + {generate_sequence_number(), column_count}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffers.emplace_back())); + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } +} + +struct connection_context::datatype_properties_type { + classic_protocol::collation::value_type collation; + std::uint32_t column_length; + classic_protocol::field_type::value_type field_type; + classic_protocol::column_def::value_type column_flags; + std::uint8_t decimals; +}; + +template struct standard_type_translator { + static constexpr connection_context::datatype_properties_type translate() { + classic_protocol::collation::value_type collation{}; + if constexpr (std::is_integral_v) { + collation = classic_protocol::collation::Binary; + } else if constexpr (std::is_same_v) { + collation = classic_protocol::collation::Latin1SwedishCi; + } + + std::uint32_t column_length{}; + if constexpr (std::is_integral_v) { + column_length = std::numeric_limits::digits10 + 1; + } else if constexpr (std::is_same_v) { + // just some default for string types, can be adjusted as needed + constexpr std::size_t default_varchar_column_length{255U}; + column_length = default_varchar_column_length; + } + + classic_protocol::field_type::value_type field_type{}; + if constexpr (std::is_integral_v) { + field_type = classic_protocol::field_type::LongLong; + } else if constexpr (std::is_same_v) { + field_type = classic_protocol::field_type::VarString; + } + + classic_protocol::column_def::value_type column_flags{}; + if constexpr (std::is_integral_v) { + column_flags |= classic_protocol::column_def::numeric; + if constexpr (std::is_unsigned_v) { + column_flags |= classic_protocol::column_def::is_unsigned; + } + } else if constexpr (std::is_same_v) { + // do nothing for string types + } + column_flags |= classic_protocol::column_def::not_null; + + std::uint8_t decimals{}; + if constexpr (std::is_integral_v) { + // for integral types it should be 0 + decimals = 0U; + } else if constexpr (std::is_same_v) { + constexpr std::uint8_t magic_varchar_decimals{31U}; + decimals = magic_varchar_decimals; + } + return {.collation = collation, + .column_length = column_length, + .field_type = field_type, + .column_flags = column_flags, + .decimals = decimals}; + } +}; + +template struct standard_type_translator> { + static constexpr connection_context::datatype_properties_type + translate() noexcept { + auto result{standard_type_translator::translate()}; + // for optional types we can just use the same translation as for the + // underlying type, but without the 'not_null' flag + result.column_flags &= ~classic_protocol::column_def::not_null; + return result; + } +}; + +template +static constexpr connection_context::datatype_properties_type + datatype_properties{standard_type_translator::translate()}; + +template +const connection_context::datatype_properties_type & +connection_context::get_datatype_properties() noexcept { + return datatype_properties; +} + +template const connection_context::datatype_properties_type & +connection_context::get_datatype_properties() noexcept; +template const connection_context::datatype_properties_type & +connection_context::get_datatype_properties< + std::optional>() noexcept; +template const connection_context::datatype_properties_type & +connection_context::get_datatype_properties() noexcept; +template const connection_context::datatype_properties_type & +connection_context::get_datatype_properties< + std::optional>() noexcept; + +void connection_context::encode_resultset_column_definition_internal( + connection_buffer_container &result_buffers, std::string_view db_name, + std::string_view table_name, const column_name_pair &column_name, + const datatype_properties_type &datatype_properties) { + // 'binary' collation for integer types + classic_protocol::message::server::ColumnMeta column_definition{ + "def" /* catalog */, + std::string{db_name} /* schema */, + std::string{table_name} /* table */, + std::string{table_name} /* orig_table */, + std::string{column_name.first} /* name */, + std::string{column_name.second} /* orig_name */, + datatype_properties.collation /* collation */, + datatype_properties.column_length /* column_length */, + datatype_properties.field_type /* type */, + datatype_properties.column_flags /* flags */, + datatype_properties.decimals /* decimals */ + }; + using column_definition_frame = classic_protocol::frame::Frame< + classic_protocol::message::server::ColumnMeta>; + const auto encode_result = classic_protocol::encode( + {generate_sequence_number(), std::move(column_definition)}, + get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffers.emplace_back())); + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } +} + +void connection_context::encode_resultset_intermediate_eof_internal( + connection_buffer_container &result_buffers) { + const auto encode_result = classic_protocol::encode< + classic_protocol::frame::Frame>( + {generate_sequence_number(), {}}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffers.emplace_back())); + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } +} + +void connection_context::encode_resultset_textualized_row_internal( + connection_buffer_container &result_buffers, + const optional_string_collection &row_values) { + static_assert( + std::is_same_v); + using row_frame = + classic_protocol::frame::Frame; + const auto encode_result = classic_protocol::encode( + {generate_sequence_number(), {row_values}}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffers.emplace_back())); + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } +} + +void connection_context::encode_resultset_eof_internal( + connection_buffer_container &result_buffers) { + classic_protocol::message::server::Eof eof_packet{}; + const classic_protocol::status::value_type eof_status_flags{ + classic_protocol::status::autocommit}; + eof_packet.status_flags(eof_status_flags); + using eof_frame = + classic_protocol::frame::Frame; + const auto encode_result = classic_protocol::encode( + {generate_sequence_number(), eof_packet}, get_shared_capabilities(), + boost::asio::dynamic_buffer(result_buffers.emplace_back())); + if (!encode_result) { + throw boost::system::system_error{encode_result.error()}; + } +} + +} // namespace minimysql diff --git a/src/minimysql/connection_context.hpp b/src/minimysql/connection_context.hpp new file mode 100644 index 0000000..cdf43eb --- /dev/null +++ b/src/minimysql/connection_context.hpp @@ -0,0 +1,307 @@ +// Copyright (c) 2023-2026 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 MINIMYSQL_CONNECTION_CONTEXT_HPP +#define MINIMYSQL_CONNECTION_CONTEXT_HPP + +#include "minimysql/connection_context_fwd.hpp" // IWYU pragma: export + +#include +#include +#include +#include +#include +#include + +namespace minimysql { + +class connection_context { +public: + struct datatype_properties_type; + + // Constants for the greetings packet + static constexpr std::uint8_t default_server_protocol_version{10U}; + static constexpr std::string_view default_server_version{"9.7.0-pbs"}; + static constexpr std::uint16_t default_server_status_flags{0U}; + static constexpr std::uint8_t default_server_collation{0U}; + static constexpr std::string_view default_server_auth_method{ + "caching_sha2_password"}; + + connection_context(std::string_view server_username, + std::string_view server_password); + + [[nodiscard]] const std::string &get_server_username() const noexcept { + return server_username_; + } + [[nodiscard]] const std::string &get_server_password() const noexcept { + return server_password_; + } + [[nodiscard]] bool check_client_authentication() const; + + [[nodiscard]] std::uint32_t get_connection_id() const noexcept { + return connection_id_; + } + [[nodiscard]] std::uint8_t get_sequence_number() const noexcept { + return sequence_number_; + } + + [[nodiscard]] const capability_bitset & + get_server_capabilities() const noexcept { + return server_capabilities_; + } + [[nodiscard]] const capability_bitset & + get_client_capabilities() const noexcept { + return client_capabilities_; + } + [[nodiscard]] capability_bitset get_shared_capabilities() const noexcept { + return client_capabilities_ & server_capabilities_; + } + [[nodiscard]] bool check_shared_plugin_auth_supported() const; + [[nodiscard]] bool + check_shared_text_result_with_session_tracking_supported() const; + + [[nodiscard]] const std::string &get_server_auth_method() const noexcept { + return server_auth_method_; + } + [[nodiscard]] const std::string & + get_server_auth_method_data() const noexcept { + return server_auth_method_data_; + } + + [[nodiscard]] const std::string &get_client_auth_method() const noexcept { + return client_auth_method_; + } + [[nodiscard]] const std::string & + get_client_auth_method_data() const noexcept { + return client_auth_method_data_; + } + + [[nodiscard]] const std::string &get_client_username() const noexcept { + return client_username_; + } + [[nodiscard]] const std::string &get_client_schema() const noexcept { + return client_schema_; + } + [[nodiscard]] std::uint8_t get_client_collation() const noexcept { + return client_collation_; + } + [[nodiscard]] std::uint32_t get_client_max_packet_size() const noexcept { + return client_max_packet_size_; + } + [[nodiscard]] const std::string &get_client_attributes() const noexcept { + return client_attributes_; + } + + [[nodiscard]] client_command_type get_client_mysql_command() const noexcept { + return client_command_; + } + [[nodiscard]] const std::string &get_client_statement() const noexcept { + return client_statement_; + } + + [[nodiscard]] std::uint16_t get_binlog_flags() const noexcept { + return binlog_flags_; + } + [[nodiscard]] bool check_binlog_non_blocking_dump() const noexcept; + [[nodiscard]] std::uint32_t get_binlog_server_id() const noexcept { + return binlog_server_id_; + } + [[nodiscard]] const std::string &get_binlog_filename() const noexcept { + return binlog_filename_; + } + [[nodiscard]] std::uint64_t get_binlog_position() const noexcept { + return binlog_position_; + } + + [[nodiscard]] static std::size_t get_frame_header_length() noexcept; + [[nodiscard]] static std::size_t + parse_frame_header(const connection_buffer_type &payload); + + [[nodiscard]] connection_buffer_type generate_encoded_server_greeting(); + void parse_client_greeting(const connection_buffer_type &payload); + + [[nodiscard]] connection_buffer_type generate_encoded_fast_auth(); + [[nodiscard]] connection_buffer_type generate_encoded_ok(); + [[nodiscard]] connection_buffer_type generate_encoded_eof(); + [[nodiscard]] connection_buffer_type + generate_encoded_error(std::uint16_t error_code, std::string_view message, + std::string_view sql_state); + [[nodiscard]] connection_buffer_type generate_encoded_access_denied(); + [[nodiscard]] connection_buffer_type generate_encoded_unknown_command(); + [[nodiscard]] connection_buffer_type generate_encoded_syntax_error(); + + [[nodiscard]] connection_buffer_type + generate_encoded_binlog_event(std::string_view event_data); + + void enter_command_loop_iteration() noexcept { + sequence_number_ = 0U; + client_command_ = client_command_type::unknown; + client_statement_.clear(); + } + void parse_client_command(const connection_buffer_type &payload); + + template + [[nodiscard]] connection_buffer_container encode_resultset( + const std::vector> &rows, + std::span column_names) { + static constexpr std::size_t number_of_columns{ + std::tuple_size_v>}; + const std::size_t number_of_rows{rows.size()}; + + const bool text_result_with_session_tracking_supported{ + check_shared_text_result_with_session_tracking_supported()}; + connection_buffer_container result_buffers{}; + // 1 for column count, 1 per column + (0 or 1) for intermediate EOF, 1 per + // row + 1 for EOF + result_buffers.reserve( + 1UZ + number_of_columns + + (text_result_with_session_tracking_supported ? 1UZ : 0UZ) + + number_of_rows + 1UZ); + + // part 1: column count + encode_resultset_number_of_columns_internal(result_buffers, + number_of_columns); + + // part 2: column definition(s) + encode_resultset_column_definitions_internal( + result_buffers, "db", "tbl", column_names); + + // part 2a: intermediate eof packet that is needed only when text resultset + // with session tracking is NOT supported by the client. + if (!text_result_with_session_tracking_supported) { + encode_resultset_intermediate_eof_internal(result_buffers); + } + + // part 3: row data + for (const auto &row : rows) { + encode_resultset_row_internal(result_buffers, row); + } + + // part 4: EOF + encode_resultset_eof_internal(result_buffers); + + return result_buffers; + } + +private: + static std::atomic_uint32_t next_connection_id_; + + std::string server_username_; + std::string server_password_; + + std::uint32_t connection_id_; + std::uint8_t sequence_number_{0U}; + + capability_bitset server_capabilities_{}; + capability_bitset client_capabilities_{}; + + std::string server_auth_method_{}; + std::string server_auth_method_data_{}; + + std::string client_auth_method_{}; + std::string client_auth_method_data_{}; + + std::string client_username_{}; + std::string client_schema_{}; + std::uint8_t client_collation_{0U}; + std::uint32_t client_max_packet_size_{0U}; + std::string client_attributes_{}; + + client_command_type client_command_{client_command_type::unknown}; + std::string client_statement_{}; + + std::uint16_t binlog_flags_{}; + std::uint32_t binlog_server_id_{}; + std::string binlog_filename_{}; + std::uint64_t binlog_position_{}; + + [[nodiscard]] static capability_bitset + get_default_server_capabilities() noexcept; + [[nodiscard]] const std::string &generate_server_auth_method_data(); + [[nodiscard]] std::uint8_t generate_sequence_number(); + void validate_and_update_sequence_number(std::uint8_t sequence_number); + + void encode_resultset_number_of_columns_internal( + connection_buffer_container &result_buffers, + std::size_t number_of_columns); + + template + static const datatype_properties_type &get_datatype_properties() noexcept; + + void encode_resultset_column_definition_internal( + connection_buffer_container &result_buffers, std::string_view db_name, + std::string_view table_name, const column_name_pair &column_name, + const datatype_properties_type &datatype_properties); + template + void encode_resultset_column_definitions_internal( + connection_buffer_container &result_buffers, + [[maybe_unused]] std::string_view db_name, + [[maybe_unused]] std::string_view table_name, + [[maybe_unused]] std::span + column_names) { + std::size_t column_index{0UZ}; + (encode_resultset_column_definition_internal( + result_buffers, db_name, table_name, column_names[column_index++], + get_datatype_properties()), + ...); + } + void encode_resultset_intermediate_eof_internal( + connection_buffer_container &result_buffers); + + using optional_string = std::optional; + using optional_string_collection = std::vector; + void encode_resultset_textualized_row_internal( + connection_buffer_container &result_buffers, + const optional_string_collection &row_values); + + template + static optional_string textualize_value(const Type &value) { + if constexpr (std::is_integral_v) { + return std::to_string(value); + } else if constexpr (std::is_same_v) { + return value; + } + } + template + static optional_string textualize_value(const std::optional &value) { + if (!value.has_value()) { + return {}; + } + return textualize_value(*value); + } + + template + void + encode_resultset_row_internal(connection_buffer_container &result_buffers, + const std::tuple &row) { + optional_string_collection textualized_row_values{}; + textualized_row_values.reserve(sizeof...(TypePack)); + const auto tuple_traversal_lambda{[&textualized_row_values, + &row]( + std::index_sequence) { + (textualized_row_values.emplace_back(textualize_value(std::get(row))), + ...); + }}; + tuple_traversal_lambda(std::index_sequence_for{}); + encode_resultset_textualized_row_internal(result_buffers, + textualized_row_values); + } + void + encode_resultset_eof_internal(connection_buffer_container &result_buffers); +}; + +} // namespace minimysql + +#endif // MINIMYSQL_CONNECTION_CONTEXT_HPP diff --git a/src/minimysql/connection_context_fwd.hpp b/src/minimysql/connection_context_fwd.hpp new file mode 100644 index 0000000..dfa57c7 --- /dev/null +++ b/src/minimysql/connection_context_fwd.hpp @@ -0,0 +1,54 @@ +// Copyright (c) 2023-2026 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 MINIMYSQL_CONNECTION_CONTEXT_FWD_HPP +#define MINIMYSQL_CONNECTION_CONTEXT_FWD_HPP + +#include +#include +#include +#include +#include + +#include + +namespace minimysql { + +using connection_buffer_type = std::string; +using connection_buffer_container = std::vector; + +inline constexpr auto number_of_capability_bits{32UZ}; +using capability_bitset = std::bitset; + +// first is column name, second is original column name +using column_name_pair = std::pair; + +// clang-format off +BOOST_DEFINE_FIXED_ENUM_CLASS(client_command_type, std::uint8_t, + query, + quit, + reset_connection, + change_user, + ping, + binlog_dump, + binlog_dump_gtid, + unknown) +// clang-format on + +class connection_context; + +} // namespace minimysql + +#endif // MINIMYSQL_CONNECTION_CONTEXT_FWD_HPP diff --git a/src/minimysql_app.cpp b/src/minimysql_app.cpp new file mode 100644 index 0000000..bc9d868 --- /dev/null +++ b/src/minimysql_app.cpp @@ -0,0 +1,772 @@ +// Copyright (c) 2023-20264 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +// Include What You Use pragma is needed here because the 'co_spawn()' +// function that is used in this file is located in the 'impl' subdirectory +// of the 'asio' headers ('boost/asio/impl/co_spawn.hpp') and should not be +// included directly, but the 'boost/asio/co_spawn.hpp' header is a public +// one that includes the 'impl' header +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include "minimysql/connection_context.hpp" + +class scope_tracer { +public: + explicit scope_tracer(std::string_view name) : name_(name) { + std::cout << "entering " << name_ << '\n'; + } + scope_tracer(const scope_tracer &) = delete; + scope_tracer &operator=(const scope_tracer &) = delete; + scope_tracer(scope_tracer &&) = delete; + scope_tracer &operator=(scope_tracer &&) = delete; + + ~scope_tracer() { std::cout << "leaving " << name_ << '\n'; } + +private: + std::string name_; +}; + +class hardcoded_event_data { +public: + using event_data_type = std::string; + static constexpr auto number_of_predefined_events{10UZ}; + using event_data_container = + std::array; + + hardcoded_event_data() + : events_{hex_to_bin(hardcoded_event_data::rotate_artificial), + hex_to_bin(hardcoded_event_data::fde), + hex_to_bin(hardcoded_event_data::previous_gtids), + hex_to_bin(hardcoded_event_data::first_gtid_log), + hex_to_bin(hardcoded_event_data::first_query), + hex_to_bin(hardcoded_event_data::second_gtid_log), + hex_to_bin(hardcoded_event_data::second_query), + hex_to_bin(hardcoded_event_data::second_table_map), + hex_to_bin(hardcoded_event_data::second_write_rows), + hex_to_bin(hardcoded_event_data::second_xid)} {} + + [[nodiscard]] const event_data_container &get_events() const noexcept { + return events_; + } + +private: + static constexpr std::string_view rotate_artificial{ + "00 00 00 00 04 01 00 00 00 28 00 00 00 00 00 00" + "00 20 00 04 00 00 00 00 00 00 00 62 69 6e 6c 6f" + "67 2e 30 30 30 30 30 31 "}; + static constexpr std::string_view fde{ + "1a b2 1c 6a 0f 01 00 00 00 7b 00 00 00 7f 00 00" + "00 00 00 04 00 38 2e 34 2e 37 2d 37 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + "00 00 00 00 00 00 00 1a b2 1c 6a 13 00 0d 00 08" + "00 00 00 00 04 00 04 00 00 00 63 00 04 1a 08 00" + "00 00 00 00 00 02 00 00 00 0a 0a 0a 2a 2a 00 12" + "34 00 0a 28 00 00 01 ee c6 67 82 "}; + static constexpr std::string_view previous_gtids{ + "1a b2 1c 6a 23 01 00 00 00 1f 00 00 00 9e 00 00" + "00 80 00 00 00 00 00 00 00 00 00 09 5d aa 16 "}; + static constexpr std::string_view first_gtid_log{ + "52 b2 1c 6a 21 01 00 00 00 4d 00 00 00 eb 00 00" + "00 00 00 01 b1 14 55 0c 5d 3d 11 f1 a7 3a 00 15" + "5d f1 f4 92 01 00 00 00 00 00 00 00 02 00 00 00" + "00 00 00 00 00 01 00 00 00 00 00 00 00 9c 7e f6" + "5f 24 53 06 cb 17 3a 01 00 80 dc f9 5c "}; + static constexpr std::string_view first_query{ + "52 b2 1c 6a 02 01 00 00 00 7e 00 00 00 69 01 00" + "00 00 00 09 00 00 00 00 00 00 00 04 00 00 2f 00" + "00 00 00 00 00 01 20 00 a0 45 00 00 00 00 06 03" + "73 74 64 04 ff 00 ff 00 ff 00 0c 01 74 65 73 74" + "00 11 06 00 00 00 00 00 00 00 12 ff 00 13 00 74" + "65 73 74 00 43 52 45 41 54 45 20 54 41 42 4c 45" + "20 74 31 28 69 64 20 53 45 52 49 41 4c 20 50 52" + "49 4d 41 52 59 20 4b 45 59 29 d9 1d 2b 2b "}; + static constexpr std::string_view second_gtid_log{ + "57 b2 1c 6a 21 01 00 00 00 4f 00 00 00 b8 01 00" + "00 00 00 00 b1 14 55 0c 5d 3d 11 f1 a7 3a 00 15" + "5d f1 f4 92 02 00 00 00 00 00 00 00 02 01 00 00" + "00 00 00 00 00 02 00 00 00 00 00 00 00 8a 35 4f" + "60 24 53 06 fc 15 01 17 3a 01 00 24 35 52 71 "}; + static constexpr std::string_view second_query{ + "57 b2 1c 6a 02 01 00 00 00 4b 00 00 00 03 02 00" + "00 08 00 09 00 00 00 00 00 00 00 04 00 00 1d 00" + "00 00 00 00 00 01 20 00 a0 45 00 00 00 00 06 03" + "73 74 64 04 ff 00 ff 00 ff 00 12 ff 00 74 65 73" + "74 00 42 45 47 49 4e c8 4c ce 80 "}; + static constexpr std::string_view second_table_map{ + "57 b2 1c 6a 13 01 00 00 00 30 00 00 00 33 02 00" + "00 00 00 73 00 00 00 00 00 01 00 04 74 65 73 74" + "00 02 74 31 00 01 08 00 00 01 01 80 21 d6 bc 8c"}; + static constexpr std::string_view second_write_rows{ + "57 b2 1c 6a 1e 01 00 00 00 2c 00 00 00 5f 02 00" + "00 00 00 73 00 00 00 00 00 01 00 02 00 01 ff 00" + "01 00 00 00 00 00 00 00 82 65 a7 86 "}; + static constexpr std::string_view second_xid{ + "57 b2 1c 6a 10 01 00 00 00 1f 00 00 00 7e 02 00" + "00 00 00 07 00 00 00 00 00 00 00 e3 36 06 8f "}; + + static std::string hex_to_bin(std::string_view hex) { + std::string filtered_hex{hex}; + std::erase(filtered_hex, ' '); + return boost::algorithm::unhex(filtered_hex); + } + + event_data_container events_; +}; + +namespace { + +constexpr std::uint16_t listening_port{3307}; +constexpr std::size_t expected_packet_size{4096}; +constexpr std::chrono::seconds session_authentication_timeout{10}; +constexpr std::chrono::seconds session_command_timeout{120}; +constexpr std::size_t max_payload_size{ + 16UZ * 1024UZ * 1024UZ}; // 16 MiB - MySQL's maximum packet size + +constexpr std::string_view default_username{"rpl"}; +constexpr std::string_view default_password{"password"}; + +void print_server_greeting( + const boost::asio::ip::tcp::endpoint &remote_endpoint, + const minimysql::connection_context &context) { + std::cout + << "generated server greeting for " << remote_endpoint << '\n' + << "[sequence_number " + << static_cast(context.get_sequence_number() - 1U) << "]\n" + << " protocol_version: " + << static_cast( + minimysql::connection_context::default_server_protocol_version) + << '\n' + << " server_version : " + << minimysql::connection_context::default_server_version << '\n' + << " connection_id : " << context.get_connection_id() << '\n' + << " collation : " + << static_cast( + minimysql::connection_context::default_server_collation) + << '\n' + << " status flags : " + << minimysql::connection_context::default_server_status_flags << '\n' + << " auth_method : " << context.get_server_auth_method() << '\n' + << " auth_method_data: " + << std::size(context.get_server_auth_method_data()) << " byte(s)\n"; +} + +void print_client_greeting( + const boost::asio::ip::tcp::endpoint &remote_endpoint, + const minimysql::connection_context &context) { + std::cout << "parsed client greeting from " << remote_endpoint << '\n' + << "[sequence_number " + << static_cast(context.get_sequence_number() - 1U) + << "]\n" + << " auth_method : " << context.get_client_auth_method() + << '\n' + << " auth_method_data: " + << std::size(context.get_client_auth_method_data()) << " byte(s)\n" + << " username : " << context.get_client_username() << '\n' + << " schema : " << context.get_client_schema() << '\n' + << " collation : " + << static_cast(context.get_client_collation()) + << '\n' + << " max_packet_size : " << context.get_client_max_packet_size() + << '\n'; +} +void print_generic(const boost::asio::ip::tcp::endpoint &remote_endpoint, + const minimysql::connection_context &context, + std::string_view label) { + std::cout << "generated " << label << " packet for " << remote_endpoint + << '\n' + << "[sequence_number " + << static_cast(context.get_sequence_number() - 1U) + << "]\n"; +} +void print_error(const boost::asio::ip::tcp::endpoint &remote_endpoint, + const minimysql::connection_context &context, + std::string_view label) { + std::cout << "generated error packet (" << label << ") for " + << remote_endpoint << '\n' + << "[sequence_number " + << static_cast(context.get_sequence_number() - 1U) + << "]\n"; +} +void print_client_command(const boost::asio::ip::tcp::endpoint &remote_endpoint, + const minimysql::connection_context &context) { + std::cout << "parsed client command from " << remote_endpoint << '\n' + << "[sequence_number " + << static_cast(context.get_sequence_number() - 1U) + << "]\n" + << " code : " + << boost::describe::enum_to_string( + context.get_client_mysql_command(), "unknown") + << '\n'; + if (context.get_client_mysql_command() == + minimysql::client_command_type::query) { + std::cout << " statement : " << context.get_client_statement() + << '\n'; + } + if (context.get_client_mysql_command() == + minimysql::client_command_type::binlog_dump) { + std::cout << " binlog blocking mode : " + << (context.check_binlog_non_blocking_dump() ? "non-" : "") + << "blocking\n"; + std::cout << " binlog server id : " << context.get_binlog_server_id() + << '\n'; + std::cout << " binlog file name : " << context.get_binlog_filename() + << '\n'; + std::cout << " binlog position : " << context.get_binlog_position() + << '\n'; + } +} + +// a helper function to log exceptions with context information +void handle_exception(std::string_view context) { + try { + throw; + } catch (const boost::system::system_error &e) { + bool logged_specific_error{false}; + const auto exception_error_code{e.code()}; + if (exception_error_code.category() == + boost::asio::error::get_system_category()) { + switch (exception_error_code.value()) { + case boost::asio::error::timed_out: + std::cout << context << ": " << e.what() << '\n'; + logged_specific_error = true; + break; + case boost::asio::error::operation_aborted: + std::cout << context << ": " << "operation aborted" << '\n'; + logged_specific_error = true; + break; + case boost::asio::error::eof: + case boost::asio::error::connection_reset: + std::cout << context << ": " << "connection closed by peer" << '\n'; + logged_specific_error = true; + break; + default: + // for other system errors we log the error code and message and rethrow + break; + } + } + + if (!logged_specific_error) { + std::cerr << "system error caught in " << context << ": " << e.code() + << '\n' + << e.what() << '\n'; + } + } catch (const std::exception &e) { + std::cerr << "exception caught in " << context << ": " << e.what() << '\n'; + } catch (...) { + std::cerr << "unknown exception caught in " << context << '\n'; + } +} + +// a helper coroutine for reading MySQL frame with a timeout - returns a tuple +// of (error_code, bytes_transferred) + +// as this coroutine is always used with co_await, it is absolutely safe to +// pass arguments by reference here +boost::asio::awaitable async_read_mysql_frame( + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + boost::asio::ip::tcp::socket &socket, + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + minimysql::connection_buffer_type &payload, + std::chrono::steady_clock::duration timeout) { + using namespace boost::asio::experimental::awaitable_operators; + + minimysql::connection_buffer_type local_payload{}; + auto payload_buffer{boost::asio::dynamic_buffer(local_payload)}; + + boost::asio::steady_timer read_timer{socket.get_executor(), timeout}; + // timed_read_result is a variant of 2 results (one form async read, one from + // timer) + auto timed_read_result{co_await ( + boost::asio::async_read( + socket, payload_buffer, + boost::asio::transfer_exactly( + minimysql::connection_context::get_frame_header_length()), + boost::asio::as_tuple(boost::asio::use_awaitable)) || + read_timer.async_wait( + boost::asio::as_tuple(boost::asio::use_awaitable)))}; + + // if timer finished first, we consider it a timeout error + if (timed_read_result.index() != 0UZ) { + throw boost::system::system_error{boost::asio::error::timed_out, + "frame header read timeout"}; + } + + // extracting the result of async_read for header + const auto &header_read_result{std::get<0UZ>(timed_read_result)}; + + // extracting the error code from the header_read_result and throwing if there + // was an error + const auto header_read_error_code{std::get<0UZ>(header_read_result)}; + if (header_read_error_code) { + throw boost::system::system_error{header_read_error_code, + "frame header read error"}; + } + + assert(std::size(local_payload) == + minimysql::connection_context::get_frame_header_length()); + assert(std::get<1UZ>(header_read_result) == + minimysql::connection_context::get_frame_header_length()); + + // checking the payload size from the header and throwing if it is larger than + // our maximum allowed size + auto payload_size{ + minimysql::connection_context::parse_frame_header(local_payload)}; + if (payload_size == max_payload_size) { + throw boost::system::system_error{boost::asio::error::message_size, + "frame payload size too large"}; + } + + // it is ok to reuse the same timer for reading the payload - calling + // expires_after() cancels any previously set timeout + read_timer.expires_after(timeout); + // reusing timed_read result for reading the payload + timed_read_result = co_await ( + boost::asio::async_read( + socket, payload_buffer, boost::asio::transfer_exactly(payload_size), + boost::asio::as_tuple(boost::asio::use_awaitable)) || + read_timer.async_wait(boost::asio::as_tuple(boost::asio::use_awaitable))); + + // if timer finished first, we consider it a timeout error + if (timed_read_result.index() != 0UZ) { + throw boost::system::system_error{boost::asio::error::timed_out, + "frame payload read timeout"}; + } + + // extracting the result of async_read for payload + const auto &payload_read_result{std::get<0UZ>(timed_read_result)}; + // extracting the error code from payload_read_result and throwing if there + // was an error + const auto payload_read_error_code{std::get<0UZ>(payload_read_result)}; + if (payload_read_error_code) { + throw boost::system::system_error{payload_read_error_code, + "frame payload read error"}; + } + assert(std::size(local_payload) == + minimysql::connection_context::get_frame_header_length() + + payload_size); + assert(std::get<1UZ>(payload_read_result) == payload_size); + + payload.swap(local_payload); +} + +// a helper coroutine for writing with a timeout - +// throws on error + +// as this coroutine is always used with co_await, it is absolutely safe to +// pass arguments by reference here +boost::asio::awaitable async_write_mysql_frame( + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + boost::asio::ip::tcp::socket &socket, + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + const minimysql::connection_buffer_type &payload, + std::chrono::steady_clock::duration timeout) { + using namespace boost::asio::experimental::awaitable_operators; + + boost::asio::steady_timer write_timer{socket.get_executor(), timeout}; + // timed_write_result is a variant of 2 results (one form async write, one + // from timer) + auto timed_write_result{ + co_await (boost::asio::async_write( + socket, boost::asio::buffer(payload), + boost::asio::as_tuple(boost::asio::use_awaitable)) || + write_timer.async_wait( + boost::asio::as_tuple(boost::asio::use_awaitable)))}; + + // if timer finished first, we consider it a timeout error + if (timed_write_result.index() != 0UZ) { + throw boost::system::system_error{boost::asio::error::timed_out, + "frame write timeout"}; + } + + // extracting the result of async_write + const auto &write_result{std::get<0UZ>(timed_write_result)}; + // extracting the error code from async_write result and throwing if there was + // an error + const auto write_error_code{std::get<0UZ>(write_result)}; + if (write_error_code) { + throw boost::system::system_error{write_error_code, "frame write error"}; + } + assert(std::get<1UZ>(write_result) == std::size(payload)); +} + +// a helper coroutine for writing a collection of frames with a timeout - +// throws on error + +// as this coroutine is always used with co_await, it is absolutely safe to +// pass arguments by reference here +boost::asio::awaitable async_write_mysql_frames( + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + boost::asio::ip::tcp::socket &socket, + // NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) + const minimysql::connection_buffer_container &payloads, + std::chrono::steady_clock::duration timeout) { + for (const auto &payload : payloads) { + co_await async_write_mysql_frame(socket, payload, timeout); + } +} + +// MySQL session handling coroutine - writes server greeting, then receives and +// parses client greeting +boost::asio::awaitable session(boost::asio::ip::tcp::socket socket) { + boost::system::error_code session_ec; + const auto remote_endpoint{socket.remote_endpoint(session_ec)}; + + const scope_tracer tracer("session " + + boost::lexical_cast(remote_endpoint)); + + try { + minimysql::connection_buffer_type data; + data.reserve(expected_packet_size); + + minimysql::connection_context context{default_username, default_password}; + + // creating and sending server greeting packet: + // protocol_version: 10 + // server_version: "9.7.0-pbs", + // connection_id: (maintained by connection_context, starts with + // 1 and is incremented for each new connection) auth_method_data: 20 + // random bytes generated by connection_context server_capabilities: + // collation: 0 (not set explicitly, client will assume the default one, + // most probably 255 utf8mb4_0900_ai_ci) status_flags: 0 auth_method: + // "caching_sha2_password" + + const auto server_greeting{context.generate_encoded_server_greeting()}; + print_server_greeting(remote_endpoint, context); + co_await async_write_mysql_frame(socket, server_greeting, + session_authentication_timeout); + std::cout << "sent server greeting (" << std::size(server_greeting) + << " bytes to " << remote_endpoint << ")\n"; + + // receiving and parsing client greeting packet: + // capabilities + // max_packet_size + // collation + // username + // auth_method_data + // schema + // auth_method_name + // attributes + + co_await async_read_mysql_frame(socket, data, + session_authentication_timeout); + std::cout << "received client greeting (" << std::size(data) + << " bytes from " << remote_endpoint << ")\n"; + context.parse_client_greeting(data); + print_client_greeting(remote_endpoint, context); + + if (!context.check_shared_plugin_auth_supported()) { + std::cout << "client does not support plugin authentication\n"; + const auto access_denied{context.generate_encoded_access_denied()}; + print_error(remote_endpoint, context, "plugin auth required"); + co_await async_write_mysql_frame(socket, access_denied, + session_authentication_timeout); + std::cout << "sent server access denied (" << std::size(access_denied) + << " bytes to " << remote_endpoint << ")\n"; + co_return; + } + + if (context.get_client_auth_method() != context.get_server_auth_method()) { + std::cout << "client requested " << context.get_client_auth_method() + << " authentication that does not match the one associated " + "with the user account (" + << context.get_server_auth_method() << ")\n"; + // TODO: send SwitchAuthentication packet + co_return; + } + if (!context.check_client_authentication()) { + std::cout << "client authentication failed for " + << context.get_client_username() << '\n'; + const auto access_denied{context.generate_encoded_access_denied()}; + print_error(remote_endpoint, context, "auth failure"); + co_await async_write_mysql_frame(socket, access_denied, + session_authentication_timeout); + std::cout << "sent server access denied (" << std::size(access_denied) + << " bytes to " << remote_endpoint << ")\n"; + co_return; + } + + std::cout << "client authentication succeeded for " + << context.get_client_username() << '\n'; + + // sending fast auth success + const auto fast_auth_success{context.generate_encoded_fast_auth()}; + print_generic(remote_endpoint, context, "auth method data (fast auth)"); + co_await async_write_mysql_frame(socket, fast_auth_success, + session_authentication_timeout); + std::cout << "sent server fast auth success (" + << std::size(fast_auth_success) << " bytes to " << remote_endpoint + << ")\n"; + + // sending server ok after successful authentication + const auto auth_ok{context.generate_encoded_ok()}; + print_generic(remote_endpoint, context, "ok (auth)"); + co_await async_write_mysql_frame(socket, auth_ok, + session_authentication_timeout); + std::cout << "sent server ok after authentication (" << std::size(auth_ok) + << " bytes to " << remote_endpoint << ")\n"; + + // defining known queries container + using query_handler_type = + std::function; + using query_container = std::unordered_map; + + const auto set_checksum_query_handler = + [](minimysql::connection_context &ctx) { + minimysql::connection_buffer_container resultset; + resultset.emplace_back(ctx.generate_encoded_ok()); + return resultset; + }; + query_container known_queries{ + {"select * from tbl", + [](minimysql::connection_context &ctx) { + using row_type = + std::tuple, + std::string, std::optional>; + using row_collection_type = std::vector; + const row_collection_type rows{{1, 100, "Alice", "Cooper"}, + {2, {}, "Bob", {}}}; + const std::array column_names{ + minimysql::column_name_pair{"id", "id"}, + minimysql::column_name_pair{"optional_id", "optional_id"}, + minimysql::column_name_pair{"name", "name"}, + minimysql::column_name_pair{"optional_name", "optional_name"}}; + return ctx.encode_resultset(rows, column_names); + }}, + {"select @@version_comment limit 1", + [](minimysql::connection_context &ctx) { + using version_comment_record = std::tuple; + using version_comment_record_collection = + std::vector; + const version_comment_record_collection records{ + {"Percona Binlog Server - GPL"}}; + const std::array column_names{ + minimysql::column_name_pair{"@@version_comment", ""}}; + return ctx.encode_resultset(records, column_names); + }}, + {"SELECT VERSION()", + [](minimysql::connection_context &ctx) { + using version_record = std::tuple; + using version_record_collection = std::vector; + const version_record_collection records{{"9.7.0"}}; + const std::array column_names{ + minimysql::column_name_pair{"VERSION()", ""}}; + return ctx.encode_resultset(records, column_names); + }}, + {"SET @source_binlog_checksum = 'NONE', @master_binlog_checksum = " + "'NONE'", + set_checksum_query_handler}, + {"SET @master_binlog_checksum = 'NONE', @source_binlog_checksum = " + "'NONE'", + set_checksum_query_handler}}; + + // starting command loop + bool terminated{false}; + while (!terminated) { + context.enter_command_loop_iteration(); + co_await async_read_mysql_frame(socket, data, session_command_timeout); + std::cout << "received client command (" << std::size(data) + << " bytes from " << remote_endpoint << ")\n"; + context.parse_client_command(data); + print_client_command(remote_endpoint, context); + + switch (context.get_client_mysql_command()) { + case minimysql::client_command_type::query: { + const auto known_query_it{ + known_queries.find(context.get_client_statement())}; + if (known_query_it != std::end(known_queries)) { + const auto resultset{known_query_it->second(context)}; + print_generic(remote_endpoint, context, "resultset"); + co_await async_write_mysql_frames(socket, resultset, + session_command_timeout); + std::cout << "sent server resultset (" << std::size(resultset) + << " frames to " << remote_endpoint << ")\n"; + } else { + // return 'syntax error' for every other query + const auto syntax_error = context.generate_encoded_syntax_error(); + print_error(remote_endpoint, context, "syntax error"); + co_await async_write_mysql_frame(socket, syntax_error, + session_command_timeout); + std::cout << "sent server syntax error (" << std::size(syntax_error) + << " bytes to " << remote_endpoint << ")\n"; + } + } break; + case minimysql::client_command_type::ping: { + const auto ok_after_ping{context.generate_encoded_ok()}; + print_generic(remote_endpoint, context, "ok (ping success)"); + co_await async_write_mysql_frame(socket, ok_after_ping, + session_command_timeout); + std::cout << "sent server ok after ping (" << std::size(ok_after_ping) + << " bytes to " << remote_endpoint << ")\n"; + } break; + case minimysql::client_command_type::binlog_dump: { + const hardcoded_event_data event_block; + for (const auto &event_data : event_block.get_events()) { + const auto event{context.generate_encoded_binlog_event(event_data)}; + print_generic(remote_endpoint, context, "binlog event"); + co_await async_write_mysql_frame(socket, event, + session_command_timeout); + std::cout << "sent server binlog event (" << std::size(event) + << " bytes to " << remote_endpoint << ")\n"; + // co_await async_read_mysql_frame(socket, data, + // session_command_timeout); std::cout << "received binlog event reply + // command (" << std::size(data) << " bytes from " << remote_endpoint + // << ")\n"; + } + const auto eof = context.generate_encoded_eof(); + print_generic(remote_endpoint, context, "binlog eof"); + co_await async_write_mysql_frame(socket, eof, session_command_timeout); + std::cout << "sent server eof (" << std::size(eof) << " bytes to " + << remote_endpoint << ")\n"; + terminated = true; + } break; + case minimysql::client_command_type::quit: { + // TODO: read EOF from the socket to make sure the client has closed the + // connection instead of just closing it from our side + terminated = true; + } break; + default: { + const auto unknown_command_error = + context.generate_encoded_unknown_command(); + print_error(remote_endpoint, context, "unknown command"); + co_await async_write_mysql_frame(socket, unknown_command_error, + session_command_timeout); + std::cout << "sent server unknown command (" + << std::size(unknown_command_error) << " bytes to " + << remote_endpoint << ")\n"; + } + } + } + } catch (...) { + const std::string context{ + "session " + boost::lexical_cast(remote_endpoint)}; + handle_exception(context); + } +} + +// listener coroutine - accepts incoming connections and spawns a session +// coroutine for each accepted connection + +// it is safe to pass the acceptor by reference here, as the instance of the +// acceptor object is guaranteed to outlive the listener coroutine, as it is +// created in main() and destroyed after the io_context.run() call returns +boost::asio::awaitable +// NOLINTNEXTLINE(cppcoreguidelines-avoid-reference-coroutine-parameters) +listener(boost::asio::ip::tcp::acceptor &acceptor) { + const scope_tracer tracer("listener"); + + auto executor = acceptor.get_executor(); + + try { + for (;;) { + boost::system::error_code listener_ec; + boost::asio::ip::tcp::socket socket = co_await acceptor.async_accept( + boost::asio::redirect_error(boost::asio::use_awaitable, listener_ec)); + if (listener_ec) { + if (listener_ec != boost::asio::error::operation_aborted && + listener_ec != boost::asio::error::bad_descriptor) { + throw boost::system::system_error{listener_ec}; + } + std::cout << "listener stopped\n"; + break; + } + + const auto remote_endpoint{socket.remote_endpoint(listener_ec)}; + std::cout << "accepted connection from " << remote_endpoint << '\n'; + + // NOLINTNEXTLINE(misc-include-cleaner) + boost::asio::co_spawn(executor, session(std::move(socket)), + boost::asio::detached); + } + } catch (...) { + handle_exception("listener"); + } +} + +} // anonymous namespace + +int main(int /* argc */, char * /* argv */[]) { + int res{EXIT_FAILURE}; + try { + std::cout << "starting mini-mysql-server" << '\n'; + boost::asio::io_context ctx; + const boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), + listening_port); + boost::asio::ip::tcp::acceptor acceptor(ctx, endpoint); + + boost::asio::signal_set signals(ctx, SIGINT, SIGTERM); + signals.async_wait([&](auto, auto) { + std::cout << "received termination signal\n"; + // stop accepting new clients; existing sessions keep running + boost::system::error_code close_ec; + acceptor.close(close_ec); + }); + + // NOLINTNEXTLINE(misc-include-cleaner) + boost::asio::co_spawn(ctx, listener(acceptor), boost::asio::detached); + const auto ctx_run_result{ctx.run()}; + std::cout << "ctx.run() returned " << ctx_run_result << '\n'; + res = EXIT_SUCCESS; + std::cout << "stopping mini-mysql-server\n"; + } catch (...) { + handle_exception("main"); + } + + return res; +} diff --git a/tests/gtid_set_test.cpp b/tests/gtid_set_test.cpp index 16a1b70..d3b170e 100644 --- a/tests/gtid_set_test.cpp +++ b/tests/gtid_set_test.cpp @@ -712,9 +712,6 @@ BOOST_AUTO_TEST_CASE(GtidSetWhitespaces) { // looks like a misconfiguration in clang-tidy-19 that doesn't know that // std::erase() for std::string is located in the system header - // TODO: re-check this when switching to clang-20 - - // NOLINTNEXTLINE(misc-include-cleaner) std::erase(gtids_str, ' '); BOOST_CHECK(gtids_str.find(' ') == std::string::npos); const auto restored_gtids{