Skip to content

capabilities is now a scoped enum #473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 98 additions & 106 deletions include/boost/mysql/impl/internal/protocol/capabilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,122 +8,114 @@
#ifndef BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP
#define BOOST_MYSQL_IMPL_INTERNAL_PROTOCOL_CAPABILITIES_HPP

#include <boost/config.hpp>

#include <cstdint>

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_;

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_LONG_PASSWORD: Use the improved version of Old Password Authentication
long_password = 1,

// 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_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
*/

// 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
// 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),
};
// clang-format on

BOOST_INLINE_CONSTEXPR capabilities optional_capabilities{CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS};
constexpr capabilities operator&(capabilities lhs, capabilities rhs)
{
return static_cast<capabilities>(static_cast<std::uint32_t>(lhs) & static_cast<std::uint32_t>(rhs));
}

constexpr capabilities operator|(capabilities lhs, capabilities rhs)
{
return static_cast<capabilities>(static_cast<std::uint32_t>(lhs) | static_cast<std::uint32_t>(rhs));
}

// Are all capabilities in subset in caps?
constexpr bool has_capabilities(capabilities caps, capabilities subset) { return (caps & subset) == subset; }

} // namespace detail
} // namespace mysql
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
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<capabilities>(boost::endian::little_to_native(res));
}

inline db_flavor parse_db_version(string_view version_string)
Expand Down Expand Up @@ -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
Expand Down
18 changes: 9 additions & 9 deletions include/boost/mysql/impl/internal/protocol/serialization.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uint32_t>(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
}
Expand All @@ -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<std::uint32_t>(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
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{};
Expand Down Expand Up @@ -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
Expand Down
48 changes: 37 additions & 11 deletions include/boost/mysql/impl/internal/sansio/handshake.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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();
}

Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
Loading