From 27e417dc2de9c75893f45f28c58c1062e992ede8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anarthal=20=28Rub=C3=A9n=20P=C3=A9rez=29?= <34971811+anarthal@users.noreply.github.com> Date: Fri, 25 Apr 2025 20:11:19 +0200 Subject: [PATCH] capabilities is now a scoped enum close #471 --- .../impl/internal/protocol/capabilities.hpp | 200 +++++++++--------- .../internal/protocol/deserialization.hpp | 4 +- .../impl/internal/protocol/serialization.hpp | 18 +- .../internal/sansio/connection_state_data.hpp | 4 +- .../mysql/impl/internal/sansio/handshake.hpp | 48 ++++- test/unit/include/test_unit/printing.hpp | 5 +- test/unit/src/utils.cpp | 4 +- test/unit/test/protocol/capabilities.cpp | 108 ++++++---- test/unit/test/protocol/deserialization.cpp | 50 ++--- test/unit/test/protocol/serialization.cpp | 30 +-- test/unit/test/sansio/handshake.cpp | 82 +++---- 11 files changed, 303 insertions(+), 250 deletions(-) diff --git a/include/boost/mysql/impl/internal/protocol/capabilities.hpp b/include/boost/mysql/impl/internal/protocol/capabilities.hpp index 431e4186..0696aa64 100644 --- a/include/boost/mysql/impl/internal/protocol/capabilities.hpp +++ b/include/boost/mysql/impl/internal/protocol/capabilities.hpp @@ -8,122 +8,114 @@ #ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP #define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP -#include - #include namespace boost { namespace mysql { namespace detail { -// Server/client capabilities -// clang-format off -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_LONG_PASSWORD = 1; // Use the improved version of Old Password Authentication -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_FOUND_ROWS = 2; // Send found rows instead of affected rows in EOF_Packet -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_LONG_FLAG = 4; // Get all column flags -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_CONNECT_WITH_DB = 8; // Database (schema) name can be specified on connect in Handshake Response Packet -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_NO_SCHEMA = 16; // Don't allow database.table.column -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_COMPRESS = 32; // Compression protocol supported -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_ODBC = 64; // Special handling of ODBC behavior -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_LOCAL_FILES = 128; // Can use LOAD DATA LOCAL -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_IGNORE_SPACE = 256; // Ignore spaces before '(' -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_PROTOCOL_41 = 512; // New 4.1 protocol -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_INTERACTIVE = 1024; // This is an interactive client -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_SSL = 2048; // Use SSL encryption for the session -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_IGNORE_SIGPIPE = 4096; // Client only flag -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_TRANSACTIONS = 8192; // Client knows about transactions -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_RESERVED = 16384; // DEPRECATED: Old flag for 4.1 protocol -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_SECURE_CONNECTION = 32768; // DEPRECATED: Old flag for 4.1 authentication, required by MariaDB -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_MULTI_STATEMENTS = (1UL << 16); // Enable/disable multi-stmt support -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_MULTI_RESULTS = (1UL << 17); // Enable/disable multi-results -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_PS_MULTI_RESULTS = (1UL << 18); // Multi-results and OUT parameters in PS-protocol -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_PLUGIN_AUTH = (1UL << 19); // Client supports plugin authentication -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_CONNECT_ATTRS = (1UL << 20); // Client supports connection attributes -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1UL << 21); // Enable authentication response packet to be larger than 255 bytes -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1UL << 22); // Don't close the connection for a user account with expired password -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_SESSION_TRACK = (1UL << 23); // Capable of handling server state change information -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_DEPRECATE_EOF = (1UL << 24); // Client no longer needs EOF_Packet and will use OK_Packet instead -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_SSL_VERIFY_SERVER_CERT = (1UL << 30); // Verify server certificate -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_OPTIONAL_RESULTSET_METADATA = (1UL << 25); // The client can handle optional metadata information in the resultset -BOOST_INLINE_CONSTEXPR std::uint32_t CLIENT_REMEMBER_OPTIONS = (1UL << 31); // Don't reset the options after an unsuccessful connect -// clang-format on - -class capabilities +enum class capabilities : std::uint32_t { - std::uint32_t value_; + // CLIENT_LONG_PASSWORD: Use the improved version of Old Password Authentication + long_password = 1, -public: - constexpr explicit capabilities(std::uint32_t value = 0) noexcept : value_(value){}; - constexpr std::uint32_t get() const noexcept { return value_; } - void set(std::uint32_t value) noexcept { value_ = value; } - constexpr bool has(std::uint32_t cap) const noexcept { return value_ & cap; } - constexpr bool has_all(capabilities other) const noexcept - { - return (value_ & other.get()) == other.get(); - } - constexpr capabilities operator|(capabilities rhs) const noexcept - { - return capabilities(value_ | rhs.value_); - } - constexpr capabilities operator&(capabilities rhs) const noexcept - { - return capabilities(value_ & rhs.value_); - } - constexpr bool operator==(const capabilities& rhs) const noexcept { return value_ == rhs.value_; } - constexpr bool operator!=(const capabilities& rhs) const noexcept { return value_ != rhs.value_; } + // CLIENT_FOUND_ROWS: Send found rows instead of affected rows in EOF_Packet + found_rows = 2, + + // CLIENT_LONG_FLAG: Get all column flags + long_flag = 4, + + // CLIENT_CONNECT_WITH_DB: Database (schema) name can be specified on connect in Handshake Response Packet + connect_with_db = 8, + + // CLIENT_NO_SCHEMA: Don't allow database.table.column + no_schema = 16, + + // CLIENT_COMPRESS: Compression protocol supported + compress = 32, + + // CLIENT_ODBC: Special handling of ODBC behavior + odbc = 64, + + // CLIENT_LOCAL_FILES: Can use LOAD DATA LOCAL + local_files = 128, + + // CLIENT_IGNORE_SPACE: Ignore spaces before '(' + ignore_space = 256, + + // CLIENT_PROTOCOL_41: New 4.1 protocol + protocol_41 = 512, + + // CLIENT_INTERACTIVE: This is an interactive client + interactive = 1024, + + // CLIENT_SSL: Use SSL encryption for the session + ssl = 2048, + + // CLIENT_IGNORE_SIGPIPE: Client only flag + ignore_sigpipe = 4096, + + // CLIENT_TRANSACTIONS: Client knows about transactions + transactions = 8192, + + // CLIENT_RESERVED: DEPRECATED: Old flag for 4.1 protocol + reserved = 16384, + + // CLIENT_SECURE_CONNECTION: DEPRECATED: Old flag for 4.1 authentication, required by MariaDB + secure_connection = 32768, + + // CLIENT_MULTI_STATEMENTS: Enable/disable multi-stmt support + multi_statements = (1UL << 16), + + // CLIENT_MULTI_RESULTS: Enable/disable multi-results + multi_results = (1UL << 17), + + // CLIENT_PS_MULTI_RESULTS: Multi-results and OUT parameters in PS-protocol + ps_multi_results = (1UL << 18), + + // CLIENT_PLUGIN_AUTH: Client supports plugin authentication + plugin_auth = (1UL << 19), + + // CLIENT_CONNECT_ATTRS: Client supports connection attributes + connect_attrs = (1UL << 20), + + // CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: Enable authentication response packet to be larger than 255 + // bytes + plugin_auth_lenenc_data = (1UL << 21), + + // CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS: Don't close the connection for a user account with expired + // password + can_handle_expired_passwords = (1UL << 22), + + // CLIENT_SESSION_TRACK: Capable of handling server state change information + session_track = (1UL << 23), + + // CLIENT_DEPRECATE_EOF: Client no longer needs EOF_Packet and will use OK_Packet instead + deprecate_eof = (1UL << 24), + + // CLIENT_SSL_VERIFY_SERVER_CERT: Verify server certificate + ssl_verify_server_cert = (1UL << 30), + + // CLIENT_OPTIONAL_RESULTSET_METADATA: The client can handle optional metadata information in the + // resultset + optional_resultset_metadata = (1UL << 25), + + // CLIENT_REMEMBER_OPTIONS: Don't reset the options after an unsuccessful connect + remember_options = (1UL << 31), }; -/* - * CLIENT_LONG_PASSWORD: unset // Use the improved version of Old Password Authentication - * CLIENT_FOUND_ROWS: unset // Send found rows instead of affected rows in EOF_Packet - * CLIENT_LONG_FLAG: unset // Get all column flags - * CLIENT_CONNECT_WITH_DB: optional // Database (schema) name can be specified on connect in - * Handshake Response Packet CLIENT_NO_SCHEMA: unset // Don't allow database.table.column - * CLIENT_COMPRESS: unset // Compression protocol supported - * CLIENT_ODBC: unset // Special handling of ODBC behavior - * CLIENT_LOCAL_FILES: unset // Can use LOAD DATA LOCAL - * CLIENT_IGNORE_SPACE: unset // Ignore spaces before '(' - * CLIENT_PROTOCOL_41: mandatory // New 4.1 protocol - * CLIENT_INTERACTIVE: unset // This is an interactive client - * CLIENT_SSL: unset // Use SSL encryption for the session - * CLIENT_IGNORE_SIGPIPE: unset // Client only flag - * CLIENT_TRANSACTIONS: unset // Client knows about transactions - * CLIENT_RESERVED: unset // DEPRECATED: Old flag for 4.1 protocol - * CLIENT_RESERVED2: unset // DEPRECATED: Old flag for 4.1 authentication - * \ CLIENT_SECURE_CONNECTION CLIENT_MULTI_STATEMENTS: unset // Enable/disable multi-stmt support - * CLIENT_MULTI_RESULTS: unset // Enable/disable multi-results - * CLIENT_PS_MULTI_RESULTS: unset // Multi-results and OUT parameters in PS-protocol - * CLIENT_PLUGIN_AUTH: mandatory // Client supports plugin authentication - * CLIENT_CONNECT_ATTRS: unset // Client supports connection attributes - * CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: mandatory // Enable authentication response packet to be - * larger than 255 bytes CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS: unset // Don't close the connection - * for a user account with expired password CLIENT_SESSION_TRACK: unset // Capable of handling - * server state change information CLIENT_DEPRECATE_EOF: mandatory // Client no longer needs - * EOF_Packet and will use OK_Packet instead CLIENT_SSL_VERIFY_SERVER_CERT: unset // Verify server - * certificate CLIENT_OPTIONAL_RESULTSET_METADATA: unset // The client can handle optional metadata - * information in the resultset CLIENT_REMEMBER_OPTIONS: unset // Don't reset the options after an - * unsuccessful connect - * - * We pay attention to: - * CLIENT_CONNECT_WITH_DB: optional // Database (schema) name can be specified on connect in - * Handshake Response Packet CLIENT_PROTOCOL_41: mandatory // New 4.1 protocol CLIENT_PLUGIN_AUTH: - * mandatory // Client supports plugin authentication CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: - * mandatory // Enable authentication response packet to be larger than 255 bytes - * CLIENT_DEPRECATE_EOF: mandatory // Client no longer needs EOF_Packet and will use OK_Packet - * instead - */ +constexpr capabilities operator&(capabilities lhs, capabilities rhs) +{ + return static_cast(static_cast(lhs) & static_cast(rhs)); +} -// clang-format off -BOOST_INLINE_CONSTEXPR capabilities mandatory_capabilities{ - CLIENT_PROTOCOL_41 | - CLIENT_PLUGIN_AUTH | - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | - CLIENT_DEPRECATE_EOF | - CLIENT_SECURE_CONNECTION -}; -// clang-format on +constexpr capabilities operator|(capabilities lhs, capabilities rhs) +{ + return static_cast(static_cast(lhs) | static_cast(rhs)); +} -BOOST_INLINE_CONSTEXPR capabilities optional_capabilities{CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS}; +// Are all capabilities in subset in caps? +constexpr bool has_capabilities(capabilities caps, capabilities subset) { return (caps & subset) == subset; } } // namespace detail } // namespace mysql diff --git a/include/boost/mysql/impl/internal/protocol/deserialization.hpp b/include/boost/mysql/impl/internal/protocol/deserialization.hpp index 004385b6..899ba15e 100644 --- a/include/boost/mysql/impl/internal/protocol/deserialization.hpp +++ b/include/boost/mysql/impl/internal/protocol/deserialization.hpp @@ -758,7 +758,7 @@ inline capabilities compose_capabilities(string_fixed<2> low, string_fixed<2> hi auto capabilities_begin = reinterpret_cast(&res); memcpy(capabilities_begin, low.value.data(), 2); memcpy(capabilities_begin + 2, high.value.data(), 2); - return capabilities(boost::endian::little_to_native(res)); + return static_cast(boost::endian::little_to_native(res)); } inline db_flavor parse_db_version(string_view version_string) @@ -811,7 +811,7 @@ boost::mysql::error_code boost::mysql::detail::deserialize_server_hello_impl( auto cap = compose_capabilities(pack.capability_flags_low, pack.capability_flags_high); // Check minimum server capabilities to deserialize this frame - if (!cap.has(CLIENT_PLUGIN_AUTH)) + if (!has_capabilities(cap, capabilities::plugin_auth)) return client_errc::server_unsupported; // Deserialize next fields diff --git a/include/boost/mysql/impl/internal/protocol/serialization.hpp b/include/boost/mysql/impl/internal/protocol/serialization.hpp index 7c1b31bd..41a6f758 100644 --- a/include/boost/mysql/impl/internal/protocol/serialization.hpp +++ b/include/boost/mysql/impl/internal/protocol/serialization.hpp @@ -260,16 +260,16 @@ void boost::mysql::detail::execute_stmt_command::serialize(serialization_context void boost::mysql::detail::login_request::serialize(serialization_context& ctx) const { ctx.serialize_fixed( - int4{negotiated_capabilities.get()}, // client_flag - int4{max_packet_size}, // max_packet_size - int1{get_collation_first_byte(collation_id)}, // character_set - string_fixed<23>{} // filler (all zeros) + int4{static_cast(negotiated_capabilities)}, // client_flag + int4{max_packet_size}, // max_packet_size + int1{get_collation_first_byte(collation_id)}, // character_set + string_fixed<23>{} // filler (all zeros) ); ctx.serialize( string_null{username}, string_lenenc{to_string(auth_response)} // we require CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA ); - if (negotiated_capabilities.has(CLIENT_CONNECT_WITH_DB)) + if (has_capabilities(negotiated_capabilities, capabilities::connect_with_db)) { string_null{database}.serialize(ctx); // database } @@ -279,10 +279,10 @@ void boost::mysql::detail::login_request::serialize(serialization_context& ctx) void boost::mysql::detail::ssl_request::serialize(serialization_context& ctx) const { ctx.serialize_fixed( - int4{negotiated_capabilities.get()}, // client_flag - int4{max_packet_size}, // max_packet_size - int1{get_collation_first_byte(collation_id)}, // character_set, - string_fixed<23>{} // filler, all zeros + int4{static_cast(negotiated_capabilities)}, // client_flag + int4{max_packet_size}, // max_packet_size + int1{get_collation_first_byte(collation_id)}, // character_set, + string_fixed<23>{} // filler, all zeros ); } diff --git a/include/boost/mysql/impl/internal/sansio/connection_state_data.hpp b/include/boost/mysql/impl/internal/sansio/connection_state_data.hpp index e9c85af6..a803af98 100644 --- a/include/boost/mysql/impl/internal/sansio/connection_state_data.hpp +++ b/include/boost/mysql/impl/internal/sansio/connection_state_data.hpp @@ -59,7 +59,7 @@ struct connection_state_data db_flavor flavor{db_flavor::mysql}; // What are the connection's capabilities? - capabilities current_capabilities; + capabilities current_capabilities{}; // The current connection ID. Supplied by handshake, can be used in KILL statements std::uint32_t connection_id{}; @@ -110,7 +110,7 @@ struct connection_state_data { status = connection_status::not_connected; flavor = db_flavor::mysql; - current_capabilities = capabilities(); + current_capabilities = capabilities{}; // Metadata mode does not get reset on handshake reader.reset(); // Writer does not need reset, since every write clears previous state diff --git a/include/boost/mysql/impl/internal/sansio/handshake.hpp b/include/boost/mysql/impl/internal/sansio/handshake.hpp index 0689f194..75bfc838 100644 --- a/include/boost/mysql/impl/internal/sansio/handshake.hpp +++ b/include/boost/mysql/impl/internal/sansio/handshake.hpp @@ -32,9 +32,9 @@ namespace boost { namespace mysql { namespace detail { -inline capabilities conditional_capability(bool condition, std::uint32_t cap) +inline capabilities conditional_capability(bool condition, capabilities cap) { - return capabilities(condition ? cap : 0); + return condition ? cap : capabilities{}; } inline error_code process_capabilities( @@ -44,24 +44,47 @@ inline error_code process_capabilities( bool transport_supports_ssl ) { + // The capabilities that we absolutely require. These are always set except in extremely old servers + constexpr capabilities mandatory_capabilities = + // We don't speak the older protocol + capabilities::protocol_41 | + + // We only know how to deserialize the hello frame if this is set + capabilities::plugin_auth | + + // Same as above + capabilities::plugin_auth_lenenc_data | + + // This makes processing execute responses easier + capabilities::deprecate_eof | + + // Used in MariaDB to signal 4.1 protocol. Always set in MySQL, too + capabilities::secure_connection; + + // The capabilities that we support but don't require + constexpr capabilities optional_capabilities = capabilities::multi_results | + capabilities::ps_multi_results; + auto ssl = transport_supports_ssl ? params.ssl() : ssl_mode::disable; capabilities server_caps = hello.server_capabilities; - capabilities required_caps = mandatory_capabilities | - conditional_capability(!params.database().empty(), CLIENT_CONNECT_WITH_DB) | - conditional_capability(params.multi_queries(), CLIENT_MULTI_STATEMENTS) | - conditional_capability(ssl == ssl_mode::require, CLIENT_SSL); - if (required_caps.has(CLIENT_SSL) && !server_caps.has(CLIENT_SSL)) + capabilities + required_caps = mandatory_capabilities | + conditional_capability(!params.database().empty(), capabilities::connect_with_db) | + conditional_capability(params.multi_queries(), capabilities::multi_statements) | + conditional_capability(ssl == ssl_mode::require, capabilities::ssl); + if (has_capabilities(required_caps, capabilities::ssl) && + !has_capabilities(server_caps, capabilities::ssl)) { // This happens if the server doesn't have SSL configured. This special // error code helps users diagnosing their problem a lot (server_unsupported doesn't). return make_error_code(client_errc::server_doesnt_support_ssl); } - else if (!server_caps.has_all(required_caps)) + else if (!has_capabilities(server_caps, required_caps)) { return make_error_code(client_errc::server_unsupported); } negotiated_caps = server_caps & (required_caps | optional_capabilities | - conditional_capability(ssl == ssl_mode::enable, CLIENT_SSL)); + conditional_capability(ssl == ssl_mode::enable, capabilities::ssl)); return error_code(); } @@ -89,7 +112,10 @@ class handshake_algo } // Once the handshake is processed, the capabilities are stored in the connection state - bool use_ssl(const connection_state_data& st) const { return st.current_capabilities.has(CLIENT_SSL); } + bool use_ssl(const connection_state_data& st) const + { + return has_capabilities(st.current_capabilities, capabilities::ssl); + } error_code process_handshake( connection_state_data& st, @@ -104,7 +130,7 @@ class handshake_algo return err; // Check capabilities - capabilities negotiated_caps; + capabilities negotiated_caps{}; err = process_capabilities(hparams_, hello, negotiated_caps, st.tls_supported); if (err) return err; diff --git a/test/unit/include/test_unit/printing.hpp b/test/unit/include/test_unit/printing.hpp index c1134289..eb06bf1d 100644 --- a/test/unit/include/test_unit/printing.hpp +++ b/test/unit/include/test_unit/printing.hpp @@ -8,6 +8,7 @@ #ifndef BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP #define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP +#include #include namespace boost { @@ -20,8 +21,8 @@ std::ostream& operator<<(std::ostream& os, address_type value); namespace detail { // capabilities -class capabilities; -std::ostream& operator<<(std::ostream& os, const capabilities& caps); +enum class capabilities : std::uint32_t; +std::ostream& operator<<(std::ostream& os, capabilities caps); // db_flavor enum class db_flavor; diff --git a/test/unit/src/utils.cpp b/test/unit/src/utils.cpp index 0b6faf4a..c483d741 100644 --- a/test/unit/src/utils.cpp +++ b/test/unit/src/utils.cpp @@ -611,9 +611,9 @@ static const char* to_string(address_type v) std::ostream& boost::mysql::operator<<(std::ostream& os, address_type v) { return os << ::to_string(v); } // capabilities -std::ostream& boost::mysql::detail::operator<<(std::ostream& os, const capabilities& v) +std::ostream& boost::mysql::detail::operator<<(std::ostream& os, capabilities v) { - return os << "capabilities{" << v.get() << "}"; + return os << "capabilities{" << static_cast(v) << "}"; } // db_flavor diff --git a/test/unit/test/protocol/capabilities.cpp b/test/unit/test/protocol/capabilities.cpp index 878bf99f..62e0ee87 100644 --- a/test/unit/test/protocol/capabilities.cpp +++ b/test/unit/test/protocol/capabilities.cpp @@ -9,64 +9,86 @@ #include -using namespace boost::mysql::detail; +#include "test_unit/printing.hpp" + +using namespace boost::mysql; +using detail::capabilities; +using detail::has_capabilities; + +namespace { BOOST_AUTO_TEST_SUITE(test_capabilities) -constexpr capabilities rhs{CLIENT_CONNECT_WITH_DB | CLIENT_SSL | CLIENT_COMPRESS}; - -BOOST_AUTO_TEST_CASE(has_bit_set) +BOOST_AUTO_TEST_CASE(operator_or) { - capabilities caps(CLIENT_COMPRESS); - BOOST_TEST(caps.has(CLIENT_COMPRESS)); + // Two != flags + BOOST_TEST((capabilities::long_password | capabilities::long_flag) == static_cast(5)); + + // Same flag + BOOST_TEST((capabilities::long_flag | capabilities::long_flag) == capabilities::long_flag); + + // Big values + BOOST_TEST( + (capabilities::long_password | capabilities::remember_options) == + static_cast(1 | (1 << 31)) + ); } -BOOST_AUTO_TEST_CASE(has_bit_not_set) +BOOST_AUTO_TEST_CASE(operator_and) { - capabilities caps(CLIENT_COMPRESS); - BOOST_TEST(!caps.has(CLIENT_SSL)); + // Single flag present + BOOST_TEST((static_cast(5) & capabilities::long_password) == capabilities::long_password); + + // Single flag absent + BOOST_TEST((static_cast(5) & capabilities::odbc) == capabilities{}); + + // Multiple flags + BOOST_TEST( + (static_cast(11) & static_cast(67)) == static_cast(3) + ); + + // Big values + BOOST_TEST( + (static_cast(0xffffffff) & capabilities::remember_options) == + capabilities::remember_options + ); } -BOOST_AUTO_TEST_CASE(has_multiple_bits_set) +BOOST_AUTO_TEST_CASE(has_capabilities_) { - capabilities caps(CLIENT_CONNECT_WITH_DB | CLIENT_SSL | CLIENT_COMPRESS); - for (int i = 0; i < 32; ++i) - { - std::uint32_t cap_bit = 1 << i; - bool is_set = cap_bit == CLIENT_CONNECT_WITH_DB || cap_bit == CLIENT_SSL || - cap_bit == CLIENT_COMPRESS; - BOOST_TEST(caps.has(cap_bit) == is_set); - } -} + constexpr auto search = capabilities::connect_with_db | capabilities::ssl | capabilities::compress; -BOOST_AUTO_TEST_CASE(has_all_has_none) -{ - capabilities lhs(0); - BOOST_TEST(!lhs.has_all(rhs)); -} + // No capabilities present + BOOST_TEST(!has_capabilities(capabilities{}, search)); -BOOST_AUTO_TEST_CASE(has_all_has_some_but_not_all) -{ - capabilities lhs(CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS); - BOOST_TEST(!lhs.has_all(rhs)); -} + // Some present, but not all + BOOST_TEST(!has_capabilities(capabilities::connect_with_db | capabilities::compress, search)); -BOOST_AUTO_TEST_CASE(has_all_has_some_but_not_all_plus_unrelated) -{ - capabilities lhs(CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS | CLIENT_TRANSACTIONS); - BOOST_TEST(!lhs.has_all(rhs)); -} + // Some present, but not all. Some unrelated are present + BOOST_TEST(!has_capabilities( + capabilities::connect_with_db | capabilities::compress | capabilities::long_flag, + search + )); -BOOST_AUTO_TEST_CASE(has_all_has_only_the_requested_ones) -{ - capabilities lhs(rhs); - BOOST_TEST(lhs.has_all(rhs)); -} + // Only the requested ones are present + BOOST_TEST(has_capabilities(search, search)); -BOOST_AUTO_TEST_CASE(has_all_has_the_requested_ones_and_others) -{ - capabilities lhs = rhs | capabilities(CLIENT_TRANSACTIONS); - BOOST_TEST(lhs.has_all(rhs)); + // Has the requested ones, plus extra ones + BOOST_TEST(has_capabilities(static_cast(0xffffffff), search)); + + // Searching for only one capability works + BOOST_TEST( + has_capabilities(capabilities::connect_with_db | capabilities::compress, capabilities::compress) + ); + BOOST_TEST( + !has_capabilities(capabilities::connect_with_db | capabilities::compress, capabilities::long_flag) + ); + + // Searching for the empty set always returns true + BOOST_TEST(has_capabilities(capabilities::connect_with_db | capabilities::compress, capabilities{})); + BOOST_TEST(has_capabilities(static_cast(0xffffffff), capabilities{})); } BOOST_AUTO_TEST_SUITE_END() // test_capabilities + +} // namespace \ No newline at end of file diff --git a/test/unit/test/protocol/deserialization.cpp b/test/unit/test/protocol/deserialization.cpp index 1118b32c..6b8967f9 100644 --- a/test/unit/test/protocol/deserialization.cpp +++ b/test/unit/test/protocol/deserialization.cpp @@ -1245,17 +1245,18 @@ BOOST_AUTO_TEST_CASE(deserialize_server_hello_impl_success) constexpr std::uint8_t auth_plugin_data[] = {0x52, 0x1a, 0x50, 0x3a, 0x4b, 0x12, 0x70, 0x2f, 0x03, 0x5a, 0x74, 0x05, 0x28, 0x2b, 0x7f, 0x21, 0x43, 0x4a, 0x21, 0x62}; - constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_LONG_FLAG | - CLIENT_CONNECT_WITH_DB | CLIENT_NO_SCHEMA | CLIENT_COMPRESS | CLIENT_ODBC | - CLIENT_LOCAL_FILES | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | - CLIENT_INTERACTIVE | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | - CLIENT_RESERVED | // old flag, but set in this frame - CLIENT_SECURE_CONNECTION | // old flag, but set in this frame - CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | - CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | - CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | - CLIENT_DEPRECATE_EOF | CLIENT_REMEMBER_OPTIONS; + constexpr auto caps = capabilities::long_password | capabilities::found_rows | capabilities::long_flag | + capabilities::connect_with_db | capabilities::no_schema | capabilities::compress | + capabilities::odbc | capabilities::local_files | capabilities::ignore_space | + capabilities::protocol_41 | capabilities::interactive | + capabilities::ignore_sigpipe | capabilities::transactions | + capabilities::reserved | // old flag, but set in this frame + capabilities::secure_connection | // old flag, but set in this frame + capabilities::multi_statements | capabilities::multi_results | + capabilities::ps_multi_results | capabilities::plugin_auth | + capabilities::connect_attrs | capabilities::plugin_auth_lenenc_data | + capabilities::can_handle_expired_passwords | capabilities::session_track | + capabilities::deprecate_eof | capabilities::remember_options; deserialization_buffer serialized{0x35, 0x2e, 0x37, 0x2e, 0x32, 0x37, 0x2d, 0x30, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x30, 0x2e, 0x31, 0x39, 0x2e, 0x30, 0x34, 0x2e, 0x31, 0x00, @@ -1276,7 +1277,7 @@ BOOST_AUTO_TEST_CASE(deserialize_server_hello_impl_success) // Actual value BOOST_TEST(actual.server == db_flavor::mysql); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(actual.auth_plugin_data.to_span(), auth_plugin_data); - BOOST_TEST(actual.server_capabilities == capabilities(caps)); + BOOST_TEST(actual.server_capabilities == caps); BOOST_TEST(actual.connection_id == 2u); BOOST_TEST(actual.auth_plugin_name == "mysql_native_password"); @@ -1457,17 +1458,18 @@ BOOST_AUTO_TEST_CASE(deserialize_server_hello_success) constexpr std::uint8_t auth_plugin_data[] = {0x52, 0x1a, 0x50, 0x3a, 0x4b, 0x12, 0x70, 0x2f, 0x03, 0x5a, 0x74, 0x05, 0x28, 0x2b, 0x7f, 0x21, 0x43, 0x4a, 0x21, 0x62}; - constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_LONG_FLAG | - CLIENT_CONNECT_WITH_DB | CLIENT_NO_SCHEMA | CLIENT_COMPRESS | CLIENT_ODBC | - CLIENT_LOCAL_FILES | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | - CLIENT_INTERACTIVE | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | - CLIENT_RESERVED | // old flag, but set in this frame - CLIENT_SECURE_CONNECTION | // old flag, but set in this frame - CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | - CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | - CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | - CLIENT_DEPRECATE_EOF | CLIENT_REMEMBER_OPTIONS; + constexpr auto caps = capabilities::long_password | capabilities::found_rows | capabilities::long_flag | + capabilities::connect_with_db | capabilities::no_schema | capabilities::compress | + capabilities::odbc | capabilities::local_files | capabilities::ignore_space | + capabilities::protocol_41 | capabilities::interactive | + capabilities::ignore_sigpipe | capabilities::transactions | + capabilities::reserved | // old flag, but set in this frame + capabilities::secure_connection | // old flag, but set in this frame + capabilities::multi_statements | capabilities::multi_results | + capabilities::ps_multi_results | capabilities::plugin_auth | + capabilities::connect_attrs | capabilities::plugin_auth_lenenc_data | + capabilities::can_handle_expired_passwords | capabilities::session_track | + capabilities::deprecate_eof | capabilities::remember_options; deserialization_buffer serialized{0x0a, 0x35, 0x2e, 0x37, 0x2e, 0x32, 0x37, 0x2d, 0x30, 0x75, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x30, 0x2e, 0x31, 0x39, 0x2e, 0x30, 0x34, 0x2e, 0x31, @@ -1490,7 +1492,7 @@ BOOST_AUTO_TEST_CASE(deserialize_server_hello_success) // Actual value BOOST_TEST(actual.server == db_flavor::mysql); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(actual.auth_plugin_data.to_span(), auth_plugin_data); - BOOST_TEST(actual.server_capabilities == capabilities(caps)); + BOOST_TEST(actual.server_capabilities == caps); BOOST_TEST(actual.connection_id == 2u); BOOST_TEST(actual.auth_plugin_name == "mysql_native_password"); } diff --git a/test/unit/test/protocol/serialization.cpp b/test/unit/test/protocol/serialization.cpp index 63f6867f..49dbc9b1 100644 --- a/test/unit/test/protocol/serialization.cpp +++ b/test/unit/test/protocol/serialization.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -246,13 +247,13 @@ BOOST_AUTO_TEST_CASE(login_request_) 0x35, 0xa5, 0xff, 0xdb, 0x3f, 0x48, 0xe6, 0xfc, 0x34, 0xc9} }; - constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES | - CLIENT_PROTOCOL_41 | CLIENT_INTERACTIVE | CLIENT_TRANSACTIONS | - CLIENT_SECURE_CONNECTION | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | - CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | - CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | - CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | - CLIENT_DEPRECATE_EOF; + constexpr auto caps = capabilities::long_password | capabilities::long_flag | capabilities::local_files | + capabilities::protocol_41 | capabilities::interactive | capabilities::transactions | + capabilities::secure_connection | capabilities::multi_statements | + capabilities::multi_results | capabilities::ps_multi_results | + capabilities::plugin_auth | capabilities::connect_attrs | + capabilities::plugin_auth_lenenc_data | capabilities::can_handle_expired_passwords | + capabilities::session_track | capabilities::deprecate_eof; struct { @@ -262,7 +263,7 @@ BOOST_AUTO_TEST_CASE(login_request_) } test_cases[] = { { "without_db", { - capabilities(caps), + caps, 16777216, // max packet size collations::utf8_general_ci, "root", // username @@ -277,7 +278,7 @@ BOOST_AUTO_TEST_CASE(login_request_) }, { "with_db", { - capabilities(caps | CLIENT_CONNECT_WITH_DB), + caps | capabilities::connect_with_db, 16777216, // max packet size collations::utf8_general_ci, "root", // username @@ -302,11 +303,12 @@ BOOST_AUTO_TEST_CASE(login_request_) BOOST_AUTO_TEST_CASE(ssl_request_) { - constexpr std::uint32_t caps = CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES | CLIENT_PROTOCOL_41 | - CLIENT_INTERACTIVE | CLIENT_SSL | CLIENT_TRANSACTIONS | - CLIENT_SECURE_CONNECTION | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | - CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | - CLIENT_SESSION_TRACK | (1UL << 29); + constexpr auto caps = capabilities::long_flag | capabilities::local_files | capabilities::protocol_41 | + capabilities::interactive | capabilities::ssl | capabilities::transactions | + capabilities::secure_connection | capabilities::multi_statements | + capabilities::multi_results | capabilities::ps_multi_results | + capabilities::plugin_auth | capabilities::connect_attrs | + capabilities::session_track | static_cast(1UL << 29); // Data ssl_request value{ diff --git a/test/unit/test/sansio/handshake.cpp b/test/unit/test/sansio/handshake.cpp index 34472e19..32107f08 100644 --- a/test/unit/test/sansio/handshake.cpp +++ b/test/unit/test/sansio/handshake.cpp @@ -58,12 +58,11 @@ namespace { BOOST_AUTO_TEST_SUITE(test_handshake) // Capabilities -constexpr capabilities min_caps{ - detail::CLIENT_PLUGIN_AUTH | detail::CLIENT_PROTOCOL_41 | detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | - detail::CLIENT_DEPRECATE_EOF | detail::CLIENT_SECURE_CONNECTION -}; +constexpr auto min_caps = capabilities::plugin_auth | capabilities::protocol_41 | + capabilities::plugin_auth_lenenc_data | capabilities::deprecate_eof | + capabilities::secure_connection; -constexpr capabilities tls_caps{min_caps.get() | detail::CLIENT_SSL}; +constexpr auto tls_caps = min_caps | capabilities::ssl; // Helpers to create the relevant packets class server_hello_builder @@ -119,7 +118,9 @@ public: // Capabilities is also divided in 2 parts string_fixed<2> caps_low{}; string_fixed<2> caps_high{}; - std::uint32_t caps_little = boost::endian::native_to_little(server_caps_.get()); + std::uint32_t caps_little = boost::endian::native_to_little( + static_cast(server_caps_) + ); auto* caps_begin = reinterpret_cast(&caps_little); std::copy(caps_begin, caps_begin + 2, caps_low.value.data()); std::copy(caps_begin + 2, caps_begin + 4, caps_high.value.data()); @@ -783,7 +784,7 @@ BOOST_AUTO_TEST_CASE(csha2p_securetransport_fullauth_ok) // capabilities: connect with db // -constexpr capabilities db_caps = min_caps | capabilities(detail::CLIENT_CONNECT_WITH_DB); +constexpr auto db_caps = min_caps | capabilities::connect_with_db; BOOST_AUTO_TEST_CASE(db_nonempty_supported) { @@ -853,7 +854,7 @@ BOOST_AUTO_TEST_CASE(db_empty_unsupported) // capabilities: multi_queries // -constexpr capabilities multiq_caps = min_caps | capabilities(detail::CLIENT_MULTI_STATEMENTS); +constexpr auto multiq_caps = min_caps | capabilities::multi_statements; // We request it and the server supports it BOOST_AUTO_TEST_CASE(multiq_true_supported) @@ -1061,7 +1062,6 @@ BOOST_AUTO_TEST_CASE(tls_error_unsupported) // // Base capabilities // -// TODO: having the capabilities in all uppercase likely conflicts with official headers // If the server doesn't have these, we can't talk to it BOOST_AUTO_TEST_CASE(caps_mandatory) @@ -1071,15 +1071,23 @@ BOOST_AUTO_TEST_CASE(caps_mandatory) const char* name; capabilities caps; } test_cases[] = { - {"no_protocol_41", capabilities(min_caps.get() & ~detail::CLIENT_PROTOCOL_41) }, - {"no_plugin_auth", capabilities(min_caps.get() & ~detail::CLIENT_PLUGIN_AUTH) }, + {"no_plugin_auth", + capabilities::protocol_41 | capabilities::plugin_auth_lenenc_data | capabilities::deprecate_eof | + capabilities::secure_connection }, + {"no_protocol_41", + capabilities::plugin_auth | capabilities::plugin_auth_lenenc_data | capabilities::deprecate_eof | + capabilities::secure_connection }, {"no_plugin_auth_lenenc_data", - capabilities(min_caps.get() & ~detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) }, - {"no_deprecate_eof", capabilities(min_caps.get() & ~detail::CLIENT_DEPRECATE_EOF) }, - {"no_secure_connection", capabilities(min_caps.get() & ~detail::CLIENT_SECURE_CONNECTION)}, - {"several_missing", - capabilities(detail::CLIENT_PLUGIN_AUTH | detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) }, - {"none", capabilities() }, + capabilities::plugin_auth | capabilities::protocol_41 | capabilities::deprecate_eof | + capabilities::secure_connection }, + {"no_deprecate_eof", + capabilities::plugin_auth | capabilities::protocol_41 | capabilities::plugin_auth_lenenc_data | + capabilities::secure_connection }, + {"no_secure_connection", + capabilities::plugin_auth | capabilities::protocol_41 | capabilities::plugin_auth_lenenc_data | + capabilities::deprecate_eof }, + {"several_missing", capabilities::plugin_auth | capabilities::plugin_auth_lenenc_data}, + {"none", capabilities{} }, }; for (const auto& tc : test_cases) @@ -1105,8 +1113,8 @@ BOOST_AUTO_TEST_CASE(caps_optional) const char* name; capabilities caps; } test_cases[] = { - {"multi_results", capabilities(detail::CLIENT_MULTI_RESULTS) }, - {"ps_multi_results", capabilities(detail::CLIENT_PS_MULTI_RESULTS)}, + {"multi_results", capabilities::multi_results }, + {"ps_multi_results", capabilities::ps_multi_results}, }; for (const auto& tc : test_cases) @@ -1142,24 +1150,24 @@ BOOST_AUTO_TEST_CASE(caps_ignored) const char* name; capabilities caps; } test_cases[] = { - {"long_password", capabilities(detail::CLIENT_LONG_PASSWORD) }, - {"found_rows", capabilities(detail::CLIENT_FOUND_ROWS) }, - {"long_flag", capabilities(detail::CLIENT_LONG_FLAG) }, - {"no_schema", capabilities(detail::CLIENT_NO_SCHEMA) }, - {"compress", capabilities(detail::CLIENT_COMPRESS) }, - {"odbc", capabilities(detail::CLIENT_ODBC) }, - {"local_files", capabilities(detail::CLIENT_LOCAL_FILES) }, - {"ignore_space", capabilities(detail::CLIENT_IGNORE_SPACE) }, - {"interactive", capabilities(detail::CLIENT_INTERACTIVE) }, - {"ignore_sigpipe", capabilities(detail::CLIENT_IGNORE_SIGPIPE) }, - {"transactions", capabilities(detail::CLIENT_TRANSACTIONS) }, - {"reserved", capabilities(detail::CLIENT_RESERVED) }, - {"connect_attrs", capabilities(detail::CLIENT_CONNECT_ATTRS) }, - {"can_handle_expired_passwords", capabilities(detail::CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)}, - {"session_track", capabilities(detail::CLIENT_SESSION_TRACK) }, - {"ssl_verify_server_cert", capabilities(detail::CLIENT_SSL_VERIFY_SERVER_CERT) }, - {"optional_resultset_metadata", capabilities(detail::CLIENT_OPTIONAL_RESULTSET_METADATA) }, - {"remember_options", capabilities(detail::CLIENT_REMEMBER_OPTIONS) }, + {"long_password", capabilities::long_password }, + {"found_rows", capabilities::found_rows }, + {"long_flag", capabilities::long_flag }, + {"no_schema", capabilities::no_schema }, + {"compress", capabilities::compress }, + {"odbc", capabilities::odbc }, + {"local_files", capabilities::local_files }, + {"ignore_space", capabilities::ignore_space }, + {"interactive", capabilities::interactive }, + {"ignore_sigpipe", capabilities::ignore_sigpipe }, + {"transactions", capabilities::transactions }, + {"reserved", capabilities::reserved }, + {"connect_attrs", capabilities::connect_attrs }, + {"can_handle_expired_passwords", capabilities::can_handle_expired_passwords}, + {"session_track", capabilities::session_track }, + {"ssl_verify_server_cert", capabilities::ssl_verify_server_cert }, + {"optional_resultset_metadata", capabilities::optional_resultset_metadata }, + {"remember_options", capabilities::remember_options }, }; for (const auto& tc : test_cases)