capabilities is now a scoped enum

close #471
This commit is contained in:
Anarthal (Rubén Pérez) 2025-04-25 20:11:19 +02:00 committed by GitHub
parent cbe9270253
commit 27e417dc2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 303 additions and 250 deletions

View File

@ -8,122 +8,114 @@
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP #ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP #define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP
#include <boost/config.hpp>
#include <cstdint> #include <cstdint>
namespace boost { namespace boost {
namespace mysql { namespace mysql {
namespace detail { namespace detail {
// Server/client capabilities enum class capabilities : std::uint32_t
// 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
{ {
std::uint32_t value_; // CLIENT_LONG_PASSWORD: Use the improved version of Old Password Authentication
long_password = 1,
public: // CLIENT_FOUND_ROWS: Send found rows instead of affected rows in EOF_Packet
constexpr explicit capabilities(std::uint32_t value = 0) noexcept : value_(value){}; found_rows = 2,
constexpr std::uint32_t get() const noexcept { return value_; }
void set(std::uint32_t value) noexcept { value_ = value; } // CLIENT_LONG_FLAG: Get all column flags
constexpr bool has(std::uint32_t cap) const noexcept { return value_ & cap; } long_flag = 4,
constexpr bool has_all(capabilities other) const noexcept
{ // CLIENT_CONNECT_WITH_DB: Database (schema) name can be specified on connect in Handshake Response Packet
return (value_ & other.get()) == other.get(); connect_with_db = 8,
}
constexpr capabilities operator|(capabilities rhs) const noexcept // CLIENT_NO_SCHEMA: Don't allow database.table.column
{ no_schema = 16,
return capabilities(value_ | rhs.value_);
} // CLIENT_COMPRESS: Compression protocol supported
constexpr capabilities operator&(capabilities rhs) const noexcept compress = 32,
{
return capabilities(value_ & rhs.value_); // CLIENT_ODBC: Special handling of ODBC behavior
} odbc = 64,
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_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),
}; };
/* constexpr capabilities operator&(capabilities lhs, capabilities rhs)
* 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 return static_cast<capabilities>(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
* 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
*/
// clang-format off constexpr capabilities operator|(capabilities lhs, capabilities rhs)
BOOST_INLINE_CONSTEXPR capabilities mandatory_capabilities{ {
CLIENT_PROTOCOL_41 | return static_cast<capabilities>(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
CLIENT_PLUGIN_AUTH | }
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA |
CLIENT_DEPRECATE_EOF |
CLIENT_SECURE_CONNECTION
};
// clang-format on
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 detail
} // namespace mysql } // namespace mysql

View File

@ -758,7 +758,7 @@ inline capabilities compose_capabilities(string_fixed<2> low, string_fixed<2> hi
auto capabilities_begin = reinterpret_cast<std::uint8_t*>(&res); auto capabilities_begin = reinterpret_cast<std::uint8_t*>(&res);
memcpy(capabilities_begin, low.value.data(), 2); memcpy(capabilities_begin, low.value.data(), 2);
memcpy(capabilities_begin + 2, high.value.data(), 2); memcpy(capabilities_begin + 2, high.value.data(), 2);
return capabilities(boost::endian::little_to_native(res)); return static_cast<capabilities>(boost::endian::little_to_native(res));
} }
inline db_flavor parse_db_version(string_view version_string) 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); auto cap = compose_capabilities(pack.capability_flags_low, pack.capability_flags_high);
// Check minimum server capabilities to deserialize this frame // 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; return client_errc::server_unsupported;
// Deserialize next fields // Deserialize next fields

View File

@ -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 void boost::mysql::detail::login_request::serialize(serialization_context& ctx) const
{ {
ctx.serialize_fixed( ctx.serialize_fixed(
int4{negotiated_capabilities.get()}, // client_flag int4{static_cast<std::uint32_t>(negotiated_capabilities)}, // client_flag
int4{max_packet_size}, // max_packet_size int4{max_packet_size}, // max_packet_size
int1{get_collation_first_byte(collation_id)}, // character_set int1{get_collation_first_byte(collation_id)}, // character_set
string_fixed<23>{} // filler (all zeros) string_fixed<23>{} // filler (all zeros)
); );
ctx.serialize( ctx.serialize(
string_null{username}, string_null{username},
string_lenenc{to_string(auth_response)} // we require CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 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 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 void boost::mysql::detail::ssl_request::serialize(serialization_context& ctx) const
{ {
ctx.serialize_fixed( ctx.serialize_fixed(
int4{negotiated_capabilities.get()}, // client_flag int4{static_cast<std::uint32_t>(negotiated_capabilities)}, // client_flag
int4{max_packet_size}, // max_packet_size int4{max_packet_size}, // max_packet_size
int1{get_collation_first_byte(collation_id)}, // character_set, int1{get_collation_first_byte(collation_id)}, // character_set,
string_fixed<23>{} // filler, all zeros string_fixed<23>{} // filler, all zeros
); );
} }

View File

@ -59,7 +59,7 @@ struct connection_state_data
db_flavor flavor{db_flavor::mysql}; db_flavor flavor{db_flavor::mysql};
// What are the connection's capabilities? // 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 // The current connection ID. Supplied by handshake, can be used in KILL statements
std::uint32_t connection_id{}; std::uint32_t connection_id{};
@ -110,7 +110,7 @@ struct connection_state_data
{ {
status = connection_status::not_connected; status = connection_status::not_connected;
flavor = db_flavor::mysql; flavor = db_flavor::mysql;
current_capabilities = capabilities(); current_capabilities = capabilities{};
// Metadata mode does not get reset on handshake // Metadata mode does not get reset on handshake
reader.reset(); reader.reset();
// Writer does not need reset, since every write clears previous state // Writer does not need reset, since every write clears previous state

View File

@ -32,9 +32,9 @@ namespace boost {
namespace mysql { namespace mysql {
namespace detail { 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( inline error_code process_capabilities(
@ -44,24 +44,47 @@ inline error_code process_capabilities(
bool transport_supports_ssl 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; auto ssl = transport_supports_ssl ? params.ssl() : ssl_mode::disable;
capabilities server_caps = hello.server_capabilities; capabilities server_caps = hello.server_capabilities;
capabilities required_caps = mandatory_capabilities | capabilities
conditional_capability(!params.database().empty(), CLIENT_CONNECT_WITH_DB) | required_caps = mandatory_capabilities |
conditional_capability(params.multi_queries(), CLIENT_MULTI_STATEMENTS) | conditional_capability(!params.database().empty(), capabilities::connect_with_db) |
conditional_capability(ssl == ssl_mode::require, CLIENT_SSL); conditional_capability(params.multi_queries(), capabilities::multi_statements) |
if (required_caps.has(CLIENT_SSL) && !server_caps.has(CLIENT_SSL)) 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 // 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). // error code helps users diagnosing their problem a lot (server_unsupported doesn't).
return make_error_code(client_errc::server_doesnt_support_ssl); 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); return make_error_code(client_errc::server_unsupported);
} }
negotiated_caps = server_caps & (required_caps | optional_capabilities | 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(); return error_code();
} }
@ -89,7 +112,10 @@ class handshake_algo
} }
// Once the handshake is processed, the capabilities are stored in the connection state // 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( error_code process_handshake(
connection_state_data& st, connection_state_data& st,
@ -104,7 +130,7 @@ class handshake_algo
return err; return err;
// Check capabilities // Check capabilities
capabilities negotiated_caps; capabilities negotiated_caps{};
err = process_capabilities(hparams_, hello, negotiated_caps, st.tls_supported); err = process_capabilities(hparams_, hello, negotiated_caps, st.tls_supported);
if (err) if (err)
return err; return err;

View File

@ -8,6 +8,7 @@
#ifndef BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP #ifndef BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP
#define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP #define BOOST_MYSQL_TEST_UNIT_INCLUDE_TEST_UNIT_PRINTING_HPP
#include <cstdint>
#include <iosfwd> #include <iosfwd>
namespace boost { namespace boost {
@ -20,8 +21,8 @@ std::ostream& operator<<(std::ostream& os, address_type value);
namespace detail { namespace detail {
// capabilities // capabilities
class capabilities; enum class capabilities : std::uint32_t;
std::ostream& operator<<(std::ostream& os, const capabilities& caps); std::ostream& operator<<(std::ostream& os, capabilities caps);
// db_flavor // db_flavor
enum class db_flavor; enum class db_flavor;

View File

@ -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); } std::ostream& boost::mysql::operator<<(std::ostream& os, address_type v) { return os << ::to_string(v); }
// capabilities // 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<std::uint32_t>(v) << "}";
} }
// db_flavor // db_flavor

View File

@ -9,64 +9,86 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
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) BOOST_AUTO_TEST_SUITE(test_capabilities)
constexpr capabilities rhs{CLIENT_CONNECT_WITH_DB | CLIENT_SSL | CLIENT_COMPRESS}; BOOST_AUTO_TEST_CASE(operator_or)
BOOST_AUTO_TEST_CASE(has_bit_set)
{ {
capabilities caps(CLIENT_COMPRESS); // Two != flags
BOOST_TEST(caps.has(CLIENT_COMPRESS)); BOOST_TEST((capabilities::long_password | capabilities::long_flag) == static_cast<capabilities>(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<capabilities>(1 | (1 << 31))
);
} }
BOOST_AUTO_TEST_CASE(has_bit_not_set) BOOST_AUTO_TEST_CASE(operator_and)
{ {
capabilities caps(CLIENT_COMPRESS); // Single flag present
BOOST_TEST(!caps.has(CLIENT_SSL)); BOOST_TEST((static_cast<capabilities>(5) & capabilities::long_password) == capabilities::long_password);
// Single flag absent
BOOST_TEST((static_cast<capabilities>(5) & capabilities::odbc) == capabilities{});
// Multiple flags
BOOST_TEST(
(static_cast<capabilities>(11) & static_cast<capabilities>(67)) == static_cast<capabilities>(3)
);
// Big values
BOOST_TEST(
(static_cast<capabilities>(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); constexpr auto search = capabilities::connect_with_db | capabilities::ssl | capabilities::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);
}
}
BOOST_AUTO_TEST_CASE(has_all_has_none) // No capabilities present
{ BOOST_TEST(!has_capabilities(capabilities{}, search));
capabilities lhs(0);
BOOST_TEST(!lhs.has_all(rhs));
}
BOOST_AUTO_TEST_CASE(has_all_has_some_but_not_all) // Some present, but not all
{ BOOST_TEST(!has_capabilities(capabilities::connect_with_db | capabilities::compress, search));
capabilities lhs(CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS);
BOOST_TEST(!lhs.has_all(rhs));
}
BOOST_AUTO_TEST_CASE(has_all_has_some_but_not_all_plus_unrelated) // Some present, but not all. Some unrelated are present
{ BOOST_TEST(!has_capabilities(
capabilities lhs(CLIENT_CONNECT_WITH_DB | CLIENT_COMPRESS | CLIENT_TRANSACTIONS); capabilities::connect_with_db | capabilities::compress | capabilities::long_flag,
BOOST_TEST(!lhs.has_all(rhs)); search
} ));
BOOST_AUTO_TEST_CASE(has_all_has_only_the_requested_ones) // Only the requested ones are present
{ BOOST_TEST(has_capabilities(search, search));
capabilities lhs(rhs);
BOOST_TEST(lhs.has_all(rhs));
}
BOOST_AUTO_TEST_CASE(has_all_has_the_requested_ones_and_others) // Has the requested ones, plus extra ones
{ BOOST_TEST(has_capabilities(static_cast<capabilities>(0xffffffff), search));
capabilities lhs = rhs | capabilities(CLIENT_TRANSACTIONS);
BOOST_TEST(lhs.has_all(rhs)); // 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<capabilities>(0xffffffff), capabilities{}));
} }
BOOST_AUTO_TEST_SUITE_END() // test_capabilities BOOST_AUTO_TEST_SUITE_END() // test_capabilities
} // namespace

View File

@ -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, 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}; 0x74, 0x05, 0x28, 0x2b, 0x7f, 0x21, 0x43, 0x4a, 0x21, 0x62};
constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_LONG_FLAG | constexpr auto caps = capabilities::long_password | capabilities::found_rows | capabilities::long_flag |
CLIENT_CONNECT_WITH_DB | CLIENT_NO_SCHEMA | CLIENT_COMPRESS | CLIENT_ODBC | capabilities::connect_with_db | capabilities::no_schema | capabilities::compress |
CLIENT_LOCAL_FILES | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | capabilities::odbc | capabilities::local_files | capabilities::ignore_space |
CLIENT_INTERACTIVE | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | capabilities::protocol_41 | capabilities::interactive |
CLIENT_RESERVED | // old flag, but set in this frame capabilities::ignore_sigpipe | capabilities::transactions |
CLIENT_SECURE_CONNECTION | // old flag, but set in this frame capabilities::reserved | // old flag, but set in this frame
CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | capabilities::secure_connection | // old flag, but set in this frame
CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | capabilities::multi_statements | capabilities::multi_results |
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | capabilities::ps_multi_results | capabilities::plugin_auth |
CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | capabilities::connect_attrs | capabilities::plugin_auth_lenenc_data |
CLIENT_DEPRECATE_EOF | CLIENT_REMEMBER_OPTIONS; 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, 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, 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 // Actual value
BOOST_TEST(actual.server == db_flavor::mysql); BOOST_TEST(actual.server == db_flavor::mysql);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(actual.auth_plugin_data.to_span(), auth_plugin_data); 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.connection_id == 2u);
BOOST_TEST(actual.auth_plugin_name == "mysql_native_password"); 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, 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}; 0x74, 0x05, 0x28, 0x2b, 0x7f, 0x21, 0x43, 0x4a, 0x21, 0x62};
constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_LONG_FLAG | constexpr auto caps = capabilities::long_password | capabilities::found_rows | capabilities::long_flag |
CLIENT_CONNECT_WITH_DB | CLIENT_NO_SCHEMA | CLIENT_COMPRESS | CLIENT_ODBC | capabilities::connect_with_db | capabilities::no_schema | capabilities::compress |
CLIENT_LOCAL_FILES | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | capabilities::odbc | capabilities::local_files | capabilities::ignore_space |
CLIENT_INTERACTIVE | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | capabilities::protocol_41 | capabilities::interactive |
CLIENT_RESERVED | // old flag, but set in this frame capabilities::ignore_sigpipe | capabilities::transactions |
CLIENT_SECURE_CONNECTION | // old flag, but set in this frame capabilities::reserved | // old flag, but set in this frame
CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | capabilities::secure_connection | // old flag, but set in this frame
CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | capabilities::multi_statements | capabilities::multi_results |
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | capabilities::ps_multi_results | capabilities::plugin_auth |
CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | capabilities::connect_attrs | capabilities::plugin_auth_lenenc_data |
CLIENT_DEPRECATE_EOF | CLIENT_REMEMBER_OPTIONS; 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, 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, 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 // Actual value
BOOST_TEST(actual.server == db_flavor::mysql); BOOST_TEST(actual.server == db_flavor::mysql);
BOOST_MYSQL_ASSERT_BUFFER_EQUALS(actual.auth_plugin_data.to_span(), auth_plugin_data); 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.connection_id == 2u);
BOOST_TEST(actual.auth_plugin_name == "mysql_native_password"); BOOST_TEST(actual.auth_plugin_name == "mysql_native_password");
} }

View File

@ -12,6 +12,7 @@
#include <boost/mysql/mysql_collations.hpp> #include <boost/mysql/mysql_collations.hpp>
#include <boost/mysql/string_view.hpp> #include <boost/mysql/string_view.hpp>
#include <boost/mysql/impl/internal/protocol/capabilities.hpp>
#include <boost/mysql/impl/internal/protocol/serialization.hpp> #include <boost/mysql/impl/internal/protocol/serialization.hpp>
#include <boost/core/span.hpp> #include <boost/core/span.hpp>
@ -246,13 +247,13 @@ BOOST_AUTO_TEST_CASE(login_request_)
0x35, 0xa5, 0xff, 0xdb, 0x3f, 0x48, 0xe6, 0xfc, 0x34, 0xc9} 0x35, 0xa5, 0xff, 0xdb, 0x3f, 0x48, 0xe6, 0xfc, 0x34, 0xc9}
}; };
constexpr std::uint32_t caps = CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES | constexpr auto caps = capabilities::long_password | capabilities::long_flag | capabilities::local_files |
CLIENT_PROTOCOL_41 | CLIENT_INTERACTIVE | CLIENT_TRANSACTIONS | capabilities::protocol_41 | capabilities::interactive | capabilities::transactions |
CLIENT_SECURE_CONNECTION | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | capabilities::secure_connection | capabilities::multi_statements |
CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | capabilities::multi_results | capabilities::ps_multi_results |
CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | capabilities::plugin_auth | capabilities::connect_attrs |
CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS | CLIENT_SESSION_TRACK | capabilities::plugin_auth_lenenc_data | capabilities::can_handle_expired_passwords |
CLIENT_DEPRECATE_EOF; capabilities::session_track | capabilities::deprecate_eof;
struct struct
{ {
@ -262,7 +263,7 @@ BOOST_AUTO_TEST_CASE(login_request_)
} test_cases[] = { } test_cases[] = {
{ {
"without_db", { "without_db", {
capabilities(caps), caps,
16777216, // max packet size 16777216, // max packet size
collations::utf8_general_ci, collations::utf8_general_ci,
"root", // username "root", // username
@ -277,7 +278,7 @@ BOOST_AUTO_TEST_CASE(login_request_)
}, },
{ {
"with_db", { "with_db", {
capabilities(caps | CLIENT_CONNECT_WITH_DB), caps | capabilities::connect_with_db,
16777216, // max packet size 16777216, // max packet size
collations::utf8_general_ci, collations::utf8_general_ci,
"root", // username "root", // username
@ -302,11 +303,12 @@ BOOST_AUTO_TEST_CASE(login_request_)
BOOST_AUTO_TEST_CASE(ssl_request_) BOOST_AUTO_TEST_CASE(ssl_request_)
{ {
constexpr std::uint32_t caps = CLIENT_LONG_FLAG | CLIENT_LOCAL_FILES | CLIENT_PROTOCOL_41 | constexpr auto caps = capabilities::long_flag | capabilities::local_files | capabilities::protocol_41 |
CLIENT_INTERACTIVE | CLIENT_SSL | CLIENT_TRANSACTIONS | capabilities::interactive | capabilities::ssl | capabilities::transactions |
CLIENT_SECURE_CONNECTION | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS | capabilities::secure_connection | capabilities::multi_statements |
CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH | CLIENT_CONNECT_ATTRS | capabilities::multi_results | capabilities::ps_multi_results |
CLIENT_SESSION_TRACK | (1UL << 29); capabilities::plugin_auth | capabilities::connect_attrs |
capabilities::session_track | static_cast<capabilities>(1UL << 29);
// Data // Data
ssl_request value{ ssl_request value{

View File

@ -58,12 +58,11 @@ namespace {
BOOST_AUTO_TEST_SUITE(test_handshake) BOOST_AUTO_TEST_SUITE(test_handshake)
// Capabilities // Capabilities
constexpr capabilities min_caps{ constexpr auto min_caps = capabilities::plugin_auth | capabilities::protocol_41 |
detail::CLIENT_PLUGIN_AUTH | detail::CLIENT_PROTOCOL_41 | detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA | capabilities::plugin_auth_lenenc_data | capabilities::deprecate_eof |
detail::CLIENT_DEPRECATE_EOF | detail::CLIENT_SECURE_CONNECTION 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 // Helpers to create the relevant packets
class server_hello_builder class server_hello_builder
@ -119,7 +118,9 @@ public:
// Capabilities is also divided in 2 parts // Capabilities is also divided in 2 parts
string_fixed<2> caps_low{}; string_fixed<2> caps_low{};
string_fixed<2> caps_high{}; 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<std::uint32_t>(server_caps_)
);
auto* caps_begin = reinterpret_cast<const char*>(&caps_little); auto* caps_begin = reinterpret_cast<const char*>(&caps_little);
std::copy(caps_begin, caps_begin + 2, caps_low.value.data()); std::copy(caps_begin, caps_begin + 2, caps_low.value.data());
std::copy(caps_begin + 2, caps_begin + 4, caps_high.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 // 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) BOOST_AUTO_TEST_CASE(db_nonempty_supported)
{ {
@ -853,7 +854,7 @@ BOOST_AUTO_TEST_CASE(db_empty_unsupported)
// capabilities: multi_queries // 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 // We request it and the server supports it
BOOST_AUTO_TEST_CASE(multiq_true_supported) BOOST_AUTO_TEST_CASE(multiq_true_supported)
@ -1061,7 +1062,6 @@ BOOST_AUTO_TEST_CASE(tls_error_unsupported)
// //
// Base capabilities // 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 // If the server doesn't have these, we can't talk to it
BOOST_AUTO_TEST_CASE(caps_mandatory) BOOST_AUTO_TEST_CASE(caps_mandatory)
@ -1071,15 +1071,23 @@ BOOST_AUTO_TEST_CASE(caps_mandatory)
const char* name; const char* name;
capabilities caps; capabilities caps;
} test_cases[] = { } test_cases[] = {
{"no_protocol_41", capabilities(min_caps.get() & ~detail::CLIENT_PROTOCOL_41) }, {"no_plugin_auth",
{"no_plugin_auth", capabilities(min_caps.get() & ~detail::CLIENT_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", {"no_plugin_auth_lenenc_data",
capabilities(min_caps.get() & ~detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) }, capabilities::plugin_auth | capabilities::protocol_41 | capabilities::deprecate_eof |
{"no_deprecate_eof", capabilities(min_caps.get() & ~detail::CLIENT_DEPRECATE_EOF) }, capabilities::secure_connection },
{"no_secure_connection", capabilities(min_caps.get() & ~detail::CLIENT_SECURE_CONNECTION)}, {"no_deprecate_eof",
{"several_missing", capabilities::plugin_auth | capabilities::protocol_41 | capabilities::plugin_auth_lenenc_data |
capabilities(detail::CLIENT_PLUGIN_AUTH | detail::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) }, capabilities::secure_connection },
{"none", capabilities() }, {"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) for (const auto& tc : test_cases)
@ -1105,8 +1113,8 @@ BOOST_AUTO_TEST_CASE(caps_optional)
const char* name; const char* name;
capabilities caps; capabilities caps;
} test_cases[] = { } test_cases[] = {
{"multi_results", capabilities(detail::CLIENT_MULTI_RESULTS) }, {"multi_results", capabilities::multi_results },
{"ps_multi_results", capabilities(detail::CLIENT_PS_MULTI_RESULTS)}, {"ps_multi_results", capabilities::ps_multi_results},
}; };
for (const auto& tc : test_cases) for (const auto& tc : test_cases)
@ -1142,24 +1150,24 @@ BOOST_AUTO_TEST_CASE(caps_ignored)
const char* name; const char* name;
capabilities caps; capabilities caps;
} test_cases[] = { } test_cases[] = {
{"long_password", capabilities(detail::CLIENT_LONG_PASSWORD) }, {"long_password", capabilities::long_password },
{"found_rows", capabilities(detail::CLIENT_FOUND_ROWS) }, {"found_rows", capabilities::found_rows },
{"long_flag", capabilities(detail::CLIENT_LONG_FLAG) }, {"long_flag", capabilities::long_flag },
{"no_schema", capabilities(detail::CLIENT_NO_SCHEMA) }, {"no_schema", capabilities::no_schema },
{"compress", capabilities(detail::CLIENT_COMPRESS) }, {"compress", capabilities::compress },
{"odbc", capabilities(detail::CLIENT_ODBC) }, {"odbc", capabilities::odbc },
{"local_files", capabilities(detail::CLIENT_LOCAL_FILES) }, {"local_files", capabilities::local_files },
{"ignore_space", capabilities(detail::CLIENT_IGNORE_SPACE) }, {"ignore_space", capabilities::ignore_space },
{"interactive", capabilities(detail::CLIENT_INTERACTIVE) }, {"interactive", capabilities::interactive },
{"ignore_sigpipe", capabilities(detail::CLIENT_IGNORE_SIGPIPE) }, {"ignore_sigpipe", capabilities::ignore_sigpipe },
{"transactions", capabilities(detail::CLIENT_TRANSACTIONS) }, {"transactions", capabilities::transactions },
{"reserved", capabilities(detail::CLIENT_RESERVED) }, {"reserved", capabilities::reserved },
{"connect_attrs", capabilities(detail::CLIENT_CONNECT_ATTRS) }, {"connect_attrs", capabilities::connect_attrs },
{"can_handle_expired_passwords", capabilities(detail::CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)}, {"can_handle_expired_passwords", capabilities::can_handle_expired_passwords},
{"session_track", capabilities(detail::CLIENT_SESSION_TRACK) }, {"session_track", capabilities::session_track },
{"ssl_verify_server_cert", capabilities(detail::CLIENT_SSL_VERIFY_SERVER_CERT) }, {"ssl_verify_server_cert", capabilities::ssl_verify_server_cert },
{"optional_resultset_metadata", capabilities(detail::CLIENT_OPTIONAL_RESULTSET_METADATA) }, {"optional_resultset_metadata", capabilities::optional_resultset_metadata },
{"remember_options", capabilities(detail::CLIENT_REMEMBER_OPTIONS) }, {"remember_options", capabilities::remember_options },
}; };
for (const auto& tc : test_cases) for (const auto& tc : test_cases)