From 1ad31e7a6b465e750c43f50cfe372f2184f135ba Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 7 Mar 2021 19:41:44 +0100 Subject: [PATCH] TLS cert validation, valid() after moves and others - Added connection::connection overload accepting a user-defined SSL context. - Added SSL certificate validation example. - Removed ssl_options. - Fixed an incorrect constexpr implementation (value::is_convertible_to). - Made I/O objects' valid() behavior after move operations more uniform. - Changed exception type thrown by value::get to a custom one defined in Boost.Mysql. - Changed docs link in README. closes #39 closes #27 closes #25 closes #10 --- .appveyor.yml | 1 - .dockerignore | 14 ++ .travis.yml | 1 - README.md | 4 +- doc/Jamfile | 2 +- doc/qbk/connparams.qbk | 11 +- doc/qbk/examples.qbk | 19 ++ doc/qbk/helpers/quickref.xml | 2 +- doc/xsl/custom-overrides.xsl | 7 + example/CMakeLists.txt | 1 + example/query_sync.cpp | 5 +- example/ssl.cpp | 148 +++++++++++++ example/unix_socket.cpp | 4 - example/value.cpp | 4 +- include/boost/mysql/connection.hpp | 89 +++++++- include/boost/mysql/connection_params.hpp | 38 +--- .../network_algorithms/impl/handshake.hpp | 2 +- .../boost/mysql/detail/protocol/channel.hpp | 36 +-- .../mysql/detail/protocol/impl/channel.hpp | 31 ++- .../boost/mysql/impl/prepared_statement.hpp | 1 + include/boost/mysql/impl/value.hpp | 21 +- include/boost/mysql/prepared_statement.hpp | 32 ++- include/boost/mysql/resultset.hpp | 26 ++- include/boost/mysql/value.hpp | 17 +- test/CMakeLists.txt | 33 +-- test/common/test_stream.hpp | 75 +++++++ test/integration/handshake.cpp | 89 +++++++- test/integration/integration_test_common.hpp | 15 +- .../network_functions_impl.cpp | 2 +- test/integration/prepare_statement.cpp | 61 +++++ test/integration/resultset.cpp | 64 ++++++ test/unit/connection.cpp | 69 ++++++ test/unit/prepared_statement.cpp | 125 ++++++----- test/unit/resultset.cpp | 72 ++++++ test/unit/value.cpp | 2 +- test/unit/value_constexpr.cpp | 208 ++++++++++++++++++ tools/build_docs.sh | 6 + tools/build_unix.sh | 2 + tools/docker/mariadb10.3.dockerfile | 4 +- tools/docker/mysql5.dockerfile | 2 + tools/docker/mysql8.dockerfile | 2 + tools/docker/{mariadb10.3.ssl.cnf => ssl.cnf} | 0 tools/file_headers.py | 2 +- tools/osx-ci.cnf | 3 + tools/{docker => ssl}/ca-cert.pem | 0 tools/{docker => ssl}/server-cert.pem | 0 tools/{docker => ssl}/server-key.pem | 0 47 files changed, 1174 insertions(+), 178 deletions(-) create mode 100644 .dockerignore create mode 100644 example/ssl.cpp create mode 100644 test/common/test_stream.hpp create mode 100644 test/unit/value_constexpr.cpp rename tools/docker/{mariadb10.3.ssl.cnf => ssl.cnf} (100%) rename tools/{docker => ssl}/ca-cert.pem (100%) rename tools/{docker => ssl}/server-cert.pem (100%) rename tools/{docker => ssl}/server-key.pem (100%) diff --git a/.appveyor.yml b/.appveyor.yml index 52fde6bc..e5bfda39 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -7,7 +7,6 @@ branches: except: - - value-relational-operators - gh-pages matrix: diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4e86e9b4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.cache/ +.cproject +.project +.settings/ +.pydevproject +private/ +build*/ +out/ +.vs/ +.vscode/ +compile_commands.json +.cache/ +doc/ +include/ diff --git a/.travis.yml b/.travis.yml index e6d865ee..d6a23740 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ branches: except: - - value-relational-operators - gh-pages __linux_defaults: &__linux_defaults diff --git a/README.md b/README.md index c2704fbe..1c715e53 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ -----------|---------|---------- [![Build Status](https://travis-ci.com/anarthal/mysql.png?branch=master)](https://github.com/anarthal/mysql) | [![Build status](https://ci.appveyor.com/api/projects/status/slqnb8mt91v33p1y/branch/master?svg=true)](https://ci.appveyor.com/project/anarthal/mysql/branch/master) | [![codecov](https://codecov.io/gh/anarthal/mysql/branch/master/graph/badge.svg)](https://codecov.io/gh/anarthal/mysql-asio/branch/master) -Boost.Mysql (former MySQL.Asio) is a C++11 client for the MySQL database server, based on Boost.Asio. +Boost.Mysql is a C++11 client for the MySQL database server, based on Boost.Asio. This library is in the process of being proposed for Boost. -Documentation and examples are [here](https://anarthal.github.io/boost-mysql/index.html). +Documentation and examples are [here](https://anarthal.github.io/mysql/index.html). ## Why another MySQL C++ client? diff --git a/doc/Jamfile b/doc/Jamfile index 77150bf9..c6c88b6e 100644 --- a/doc/Jamfile +++ b/doc/Jamfile @@ -276,7 +276,7 @@ boostbook mysql : mysql_doc : - "boost.root=https://www.boost.org/doc/libs/1_73_0" + "boost.root=https://www.boost.org/doc/libs/1_75_0" boost.image.src=images/proposed_for_boost.svg boost.graphics.root=images/ nav.layout=none diff --git a/doc/qbk/connparams.qbk b/doc/qbk/connparams.qbk index 2fb70d45..a1f83242 100644 --- a/doc/qbk/connparams.qbk +++ b/doc/qbk/connparams.qbk @@ -86,10 +86,8 @@ You can change it at any time issuing a __SET_NAMES__ statement [section:ssl SSL/TLS] -All connection options regarding the use of SSL/TLS live within -[refmem connection_params ssl], of type [reflink ssl_options]. -At this moment, a single option, [reflink ssl_mode], is -available. This option controls whether to use SSL/TLS or not: +When establising a connection, you can specify a [reflink ssl_mode] +value to configure whether to use SSL/TLS or not: * If set to `enable`, the connection will use TLS if available, falling back to an unencrypted connection if the server @@ -98,8 +96,9 @@ available. This option controls whether to use SSL/TLS or not: server does not support it, the connection will be refused. * If set to `disable`, the connection will never use TLS. -The server certificate is not validated. An option to do this -may be added in a future release. +You can fine-tune the SSL/TLS configuration by passing a [asioreflink ssl__context ssl::context] +object to [reflink connection]'s constructor (using [reflink2 connection.connection.overload2 this overload]). +See [link mysql.examples.ssl this section] for an example on how to do this. If you are using `enable` [reflink ssl_mode], you can use [refmem connection uses_ssl] to query whether the connection diff --git a/doc/qbk/examples.qbk b/doc/qbk/examples.qbk index e8790857..64554fe3 100644 --- a/doc/qbk/examples.qbk +++ b/doc/qbk/examples.qbk @@ -22,6 +22,7 @@ Here is a list of available examples: # [link mysql.examples.query_async_coroutines Text query, async with Boost.Coroutine coroutines] # [link mysql.examples.query_async_coroutinescpp20 Text query, async with C++20 coroutines] # [link mysql.examples.default_completion_tokens Default completion tokens] +# [link mysql.examples.ssl Advanced SSL configuration] [section:setup Setup] @@ -178,4 +179,22 @@ __assume_setup__ [endsect] +[section:ssl Advanced SSL configuration] + +This example demonstrates how to configure advanced SSL +options, like server certificate verification and hostname +validation. + +The example employs synchronous functions with +exceptions as error handling. __see_error_handling__ + +__assume_setup__ Additionally, you should run your MySQL server +with some test certificates we created your you, just for this example. +You can find them in this project's GitHub repository, under `BOOST_MYQL_ROOT/tools/ssl`. + +[import ../../example/ssl.cpp] +[example_ssl] + +[endsect] + [endsect] [/ examples] \ No newline at end of file diff --git a/doc/qbk/helpers/quickref.xml b/doc/qbk/helpers/quickref.xml index ddc41239..5e13c5b7 100644 --- a/doc/qbk/helpers/quickref.xml +++ b/doc/qbk/helpers/quickref.xml @@ -20,11 +20,11 @@ prepared_statement resultset value + bad_value_access row field_metadata connection_params execute_params - ssl_options error_info diff --git a/doc/xsl/custom-overrides.xsl b/doc/xsl/custom-overrides.xsl index 3458b8e4..b7e585fe 100644 --- a/doc/xsl/custom-overrides.xsl +++ b/doc/xsl/custom-overrides.xsl @@ -1,3 +1,10 @@ + + +#include +#include +#include +#include + +#define ASSERT(expr) \ + if (!(expr)) \ + { \ + std::cerr << "Assertion failed: " #expr << std::endl; \ + exit(1); \ + } + +// The CA file that signed the server's certificate +constexpr const char CA_PEM [] = R"%(-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM +BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw +NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM +BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S +ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 +KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 +cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC +XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU +HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE +FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK +D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX +IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ +uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f +J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd +fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX +W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk +OzBrmpfHEhF6NDU= +-----END CERTIFICATE----- +)%"; + + +void print_employee(const boost::mysql::row& employee) +{ + std::cout << "Employee '" + << employee.values()[0] << " " // first_name (type boost::string_view) + << employee.values()[1] << "' earns " // last_name (type boost::string_view) + << employee.values()[2] << " dollars yearly\n"; // salary (type double) +} + +void main_impl(int argc, char** argv) +{ + if (argc != 3) + { + std::cerr << "Usage: " << argv[0] << " \n"; + exit(1); + } + + /** + * Connection parameters that tell us where and how to connect to the MySQL server. + * There are two types of parameters: + * - TCP-level connection parameters, identifying the host and port to connect to. + * - MySQL level parameters: database credentials and schema to use. + */ + boost::asio::ip::tcp::endpoint ep ( + boost::asio::ip::address_v4::loopback(), // host + boost::mysql::default_port // port + ); + boost::mysql::connection_params params ( + argv[1], // username + argv[2], // password + "boost_mysql_examples",// database to use; leave empty or omit the parameter for no database + boost::mysql::collation::utf8_general_ci, // character set and collation to use (this is the default) + boost::mysql::ssl_mode::require // require SSL; if the server doesn't support it, fail + ); + + boost::asio::io_context ctx; + + // By default, boost::mysql::tcp_connection will create internally a + // boost::asio::ssl::context with the default options. We can override + // this behavior by passing a boost::asio::ssl::context instance to + // tcp_connection's constructor. This allows us to customize SSL behavior + // (e.g. enabling certificate validation, adding trusted CAs...). We will + // use this feature to validate the hostname in the server's certificate. + boost::asio::ssl::context ssl_ctx (boost::asio::ssl::context::tls_client); + + // Check whether the server's certificate is valid and signed by a trusted CA. + // If it's not, our handshake or connect operation will fail. + ssl_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + + // Load a trusted CA, which was used to sign the server's certificate. + // This will allow the signature verification to succeed in our example. + // You will have to run your MySQL server with the test certificates + // located under $BOOST_MYSQL_ROOT/tools/ssl/ + ssl_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + + // We expect the server certificate's common name to be "mysql". + // If it's not, the certificate will be rejected and handshake or connect will fail. + ssl_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("mysql")); + + // Pass in our pre-configured SSL context to the connection. Note that we + // can create many connections out of a single context. We need to keep the + // context alive until we finish using the connection. + boost::mysql::tcp_connection conn (ssl_ctx, ctx); + + // Connect to the server. This operation will perform the SSL handshake as part of + // it, and thus will fail if the certificate is found to be invalid. + conn.connect(ep, params); + + // We can now use the connection as we would normally do. + const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; + boost::mysql::tcp_resultset result = conn.query(sql); + std::vector employees = result.read_all(); + for (const auto& employee: employees) + { + print_employee(employee); + } + + // Cleanup + conn.close(); +} + +int main(int argc, char** argv) +{ + try + { + main_impl(argc, argv); + } + catch (const boost::system::system_error& err) + { + std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; + return 1; + } + catch (const std::exception& err) + { + std::cerr << "Error: " << err.what() << std::endl; + return 1; + } +} + +//] diff --git a/example/unix_socket.cpp b/example/unix_socket.cpp index 7cec4ccc..cd188e83 100644 --- a/example/unix_socket.cpp +++ b/example/unix_socket.cpp @@ -59,10 +59,6 @@ void main_impl(int argc, char** argv) argv[2], // password "boost_mysql_examples" // database to use; leave empty or omit the parameter for no database ); - // Note: by default, SSL will be used if the server supports it. - // connection_params accepts an optional ssl_options argument - // determining whether to use SSL or not. See ssl_options and ssl_mode - // documentation for further details on SSL. boost::asio::io_context ctx; diff --git a/example/value.cpp b/example/value.cpp index fac1409c..f58d1868 100644 --- a/example/value.cpp +++ b/example/value.cpp @@ -23,9 +23,9 @@ void example_get() ASSERT(typed_val == "hello"); try { - v.get(); // wrong type! throws boost::variant2::bad_variant_access + v.get(); // wrong type! throws boost::mysql::bad_value_access } - catch (const boost::variant2::bad_variant_access&) + catch (const boost::mysql::bad_value_access&) { } //] diff --git a/include/boost/mysql/connection.hpp b/include/boost/mysql/connection.hpp index a736b164..0af7779c 100644 --- a/include/boost/mysql/connection.hpp +++ b/include/boost/mysql/connection.hpp @@ -8,6 +8,8 @@ #ifndef BOOST_MYSQL_CONNECTION_HPP #define BOOST_MYSQL_CONNECTION_HPP +#include +#include #ifndef BOOST_MYSQL_DOXYGEN // For some arcane reason, Doxygen fails to expand Asio macros without this #include "boost/mysql/detail/protocol/channel.hpp" #include "boost/mysql/detail/protocol/protocol_types.hpp" @@ -72,19 +74,72 @@ protected: error_info& shared_info() noexcept { return get_channel().shared_info(); } public: /** - * \brief Initializing constructor. + * \brief Initializing constructor (no user-provided SSL context). * \details * As part of the initialization, a Stream object is created * by forwarding any passed in arguments to its constructor. + * If SSL ends up being used for this connection, a + * [asioreflink ssl__context ssl::context] object will be created + * on-demmand, with the minimum configuration settings to make SSL work. + * In particular, no certificate validation will be performed. If you need + * more flexibility, have a look at the other constructor overloads. + * * The constructed connection will have [refmem connection valid] * return `true`. */ - template + template< + class... Args, + class EnableIf = typename std::enable_if::value>::type + > connection(Args&&... args) : - channel_(new detail::channel(std::forward(args)...)) + channel_(new detail::channel(nullptr, std::forward(args)...)) { } + /** + * \brief Initializing constructor (user-provided SSL context). + * \details + * As part of the initialization, a Stream object is created + * by forwarding any passed in arguments to its constructor. + * If SSL ends up being used for this connection, `ctx` will be + * used to initialize a [asioreflink ssl__stream ssl::stream] object. + * By providing a SSL context you can specify extra SSL configuration options + * like certificate verification and hostname validation. You can use a single + * context in multiple connections. + * + * The provided context must be kept alive for the lifetime of the [reflink connection] + * object. Otherwise, the results are undefined. + * + * The constructed connection will have [refmem connection valid] + * return `true`. + */ + template< + class... Args, + class EnableIf = typename std::enable_if::value>::type + > + connection(boost::asio::ssl::context& ctx, Args&&... args) : + channel_(new detail::channel(&ctx, std::forward(args)...)) + { + } + + /** + * \brief Move constructor. + * \details The constructed connection will be valid if `other` is valid. + * After this operation, `other` is guaranteed to be invalid. + */ + connection(connection&& other) = default; + + /** + * \brief Move assignment. + * \details The assigned-to connection will be valid if `other` is valid. + */ + connection& operator=(connection&& rhs) = default; + +#ifndef BOOST_MYSQL_DOXYGEN + connection(const connection&) = delete; + connection& operator=(const connection&) = delete; +#endif + /** * \brief Returns `true` if the object is in a valid state. * \details This function always returns `true` except for moved-from @@ -119,8 +174,11 @@ public: /** * \brief Performs the MySQL-level handshake (sync with error code version). - * \details Does not connect the underlying stream. + * \details Does not connect the underlying stream. * Prefer [refmem socket_connection connect] if possible. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. */ void handshake(const connection_params& params, error_code& ec, error_info& info); @@ -128,6 +186,9 @@ public: * \brief Performs the MySQL-level handshake (sync with exceptions version). * \details Does not connect the underlying stream. * Prefer [refmem socket_connection connect] if possible. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. */ void handshake(const connection_params& params); @@ -138,6 +199,10 @@ public: * Prefer [refmem socket_connection async_connect] if possible. * The strings pointed to by params should be kept alive by the caller * until the operation completes, as no copy is made by the library. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. + * * The handler signature for this operation is `void(boost::mysql::error_code)`. */ template < @@ -161,6 +226,10 @@ public: * Prefer [refmem socket_connection async_connect] if possible. * The strings pointed to by params should be kept alive by the caller * until the operation completes, as no copy is made by the library. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. + * * The handler signature for this operation is `void(boost::mysql::error_code)`. */ template < @@ -417,6 +486,9 @@ public: * \details Connects the underlying socket and then performs the handshake * with the server. The underlying socket is closed in case of error. Prefer * this function to [refmem connection handshake]. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. */ void connect(const endpoint_type& endpoint, const connection_params& params, error_code& ec, error_info& info); @@ -426,6 +498,9 @@ public: * \details Connects the underlying socket and then performs the handshake * with the server. The underlying socket is closed in case of error. Prefer * this function to [refmem connection handshake]. + * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. */ void connect(const endpoint_type& endpoint, const connection_params& params); @@ -440,6 +515,9 @@ public: * The strings pointed to by params should be kept alive by the caller * until the operation completes, as no copy is made by the library. * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. + * * The handler signature for this operation is `void(boost::mysql::error_code)`. */ template < @@ -468,6 +546,9 @@ public: * The strings pointed to by params should be kept alive by the caller * until the operation completes, as no copy is made by the library. * + * If SSL certificate validation was configured (by providing a custom SSL context + * to this class' constructor) and fails, this function will fail. + * * The handler signature for this operation is `void(boost::mysql::error_code)`. */ template < diff --git a/include/boost/mysql/connection_params.hpp b/include/boost/mysql/connection_params.hpp index 08c28ff8..5afe81ee 100644 --- a/include/boost/mysql/connection_params.hpp +++ b/include/boost/mysql/connection_params.hpp @@ -36,28 +36,6 @@ enum class ssl_mode require }; -/** - * \brief Connection options regarding TLS. - * \details At the moment, contains only the [reflink ssl_mode], which - * indicates whether to use TLS on the connection or not. - */ -class ssl_options -{ - ssl_mode mode_; -public: - /** - * \brief Default and initialization constructor. - * \details By default, SSL is enabled for the connection - * if the server supports it (ssl_mode::enable). - * See [reflink ssl_mode]. - */ - explicit ssl_options(ssl_mode mode=ssl_mode::enable) noexcept: - mode_(mode) {} - - /// Retrieves the TLS mode to be used for the connection. - ssl_mode mode() const noexcept { return mode_; } -}; - /** * \brief Parameters defining how to perform the handshake @@ -70,7 +48,7 @@ class connection_params boost::string_view password_; boost::string_view database_; collation connection_collation_; - ssl_options ssl_; + ssl_mode ssl_; public: /** * \brief Initializing constructor @@ -79,20 +57,20 @@ public: * \param db Database name to use, or empty string for no database (this is the default). * \param connection_col [reflink2 collation Collation] to use for the connection. * Impacts how text queries and prepared statements are interpreted. Defaults to utf8_general_ci. - * \param opts The [reflink2 ssl_options TLS options] to use with this connection. + * \param mode The [reflink ssl_mode] to use with this connection. */ connection_params( boost::string_view username, boost::string_view password, boost::string_view db = "", collation connection_col = collation::utf8_general_ci, - const ssl_options& opts = ssl_options() + ssl_mode mode = ssl_mode::enable ) : username_(username), password_(password), database_(db), connection_collation_(connection_col), - ssl_(opts) + ssl_(mode) { } @@ -120,11 +98,11 @@ public: /// Sets the connection collation void set_connection_collation(collation value) noexcept { connection_collation_ = value; } - /// Retrieves SSL options - const ssl_options& ssl() const noexcept { return ssl_; } + /// Retrieves SSL mode + ssl_mode ssl() const noexcept { return ssl_; } - /// Sets SSL options - void set_ssl(const ssl_options& value) noexcept { ssl_ = value; } + /// Sets SSL mode + void set_ssl(ssl_mode value) noexcept { ssl_ = value; } }; } // mysql diff --git a/include/boost/mysql/detail/network_algorithms/impl/handshake.hpp b/include/boost/mysql/detail/network_algorithms/impl/handshake.hpp index 5abec537..7d9fd6ea 100644 --- a/include/boost/mysql/detail/network_algorithms/impl/handshake.hpp +++ b/include/boost/mysql/detail/network_algorithms/impl/handshake.hpp @@ -85,7 +85,7 @@ public: // Initial greeting processing error_code process_capabilities(const handshake_packet& handshake) { - auto ssl = params_.ssl().mode(); + auto ssl = params_.ssl(); capabilities server_caps (handshake.capability_falgs); capabilities required_caps = mandatory_capabilities | conditional_capability(!params_.database().empty(), CLIENT_CONNECT_WITH_DB) | diff --git a/include/boost/mysql/detail/protocol/channel.hpp b/include/boost/mysql/detail/protocol/channel.hpp index bec9e9fb..2ad475e5 100644 --- a/include/boost/mysql/detail/protocol/channel.hpp +++ b/include/boost/mysql/detail/protocol/channel.hpp @@ -28,18 +28,10 @@ template class channel { // TODO: static asserts for Stream concept - struct ssl_block - { - boost::asio::ssl::context ctx; - boost::asio::ssl::stream stream; - - ssl_block(Stream& base_stream): - ctx(boost::asio::ssl::context::tls_client), - stream (base_stream, ctx) {} - }; - + boost::asio::ssl::context* external_ctx_ {nullptr}; // if one was externally provided + boost::optional local_ctx_; // if one was not provided + boost::optional> ssl_stream_; Stream stream_; - boost::optional ssl_block_; std::uint8_t sequence_number_ {0}; std::array header_buffer_ {}; // for async ops bytestring shared_buff_; // for async ops @@ -52,7 +44,7 @@ class channel error_code process_header_read(std::uint32_t& size_to_read); // reads from header_buffer_ void process_header_write(std::uint32_t size_to_write); // writes to header_buffer_ - void create_ssl_block() { ssl_block_.emplace(stream_); } + void create_ssl_stream(); template std::size_t read_impl(BufferSeq&& buff, error_code& ec); @@ -71,8 +63,14 @@ class channel struct read_op; struct write_op; public: + channel() = default; // Simplify life if stream is default constructible, mainly for tests + template - channel(Args&&... args): stream_(std::forward(args)...) {} + channel(boost::asio::ssl::context* ctx, Args&&... args) : + external_ctx_{ctx}, + stream_(std::forward(args)...) + { + } // Executor using executor_type = typename Stream::executor_type; @@ -104,7 +102,7 @@ public: } // SSL - bool ssl_active() const noexcept { return ssl_block_.has_value(); } + bool ssl_active() const noexcept { return ssl_stream_.has_value(); } void ssl_handshake(error_code& ec); @@ -133,6 +131,16 @@ public: error_info& shared_info() noexcept { return shared_info_; } }; +// Helper class to get move semantics right for some I/O object types +template +struct null_channel_deleter +{ + void operator()(channel*) const noexcept {} +}; + +template +using channel_observer_ptr = std::unique_ptr, null_channel_deleter>; + } // detail } // mysql } // boost diff --git a/include/boost/mysql/detail/protocol/impl/channel.hpp b/include/boost/mysql/detail/protocol/impl/channel.hpp index 125ee4a3..aab08c1f 100644 --- a/include/boost/mysql/detail/protocol/impl/channel.hpp +++ b/include/boost/mysql/detail/protocol/impl/channel.hpp @@ -92,7 +92,7 @@ std::size_t boost::mysql::detail::channel::read_impl( { if (ssl_active()) { - return boost::asio::read(ssl_block_->stream, std::forward(buff), ec); + return boost::asio::read(*ssl_stream_, std::forward(buff), ec); } else { @@ -109,7 +109,7 @@ std::size_t boost::mysql::detail::channel::write_impl( { if (ssl_active()) { - return boost::asio::write(ssl_block_->stream, std::forward(buff), ec); + return boost::asio::write(*ssl_stream_, std::forward(buff), ec); } else { @@ -128,7 +128,7 @@ boost::mysql::detail::channel::async_read_impl( if (ssl_active()) { return boost::asio::async_read( - ssl_block_->stream, + *ssl_stream_, std::forward(buff), boost::asio::transfer_all(), std::forward(token) @@ -156,7 +156,7 @@ boost::mysql::detail::channel::async_write_impl( if (ssl_active()) { return boost::asio::async_write( - ssl_block_->stream, + *ssl_stream_, std::forward(buff), std::forward(token) ); @@ -400,13 +400,28 @@ boost::mysql::detail::channel::async_write( ); } +template +void boost::mysql::detail::channel::create_ssl_stream() +{ + // Determine the context to use + boost::asio::ssl::context* ctx = external_ctx_; + if (!ctx) + { + local_ctx_.emplace(boost::asio::ssl::context::tls_client); + ctx = &*local_ctx_; + } + + // Actually create the stream + ssl_stream_.emplace(stream_, *ctx); +} + template void boost::mysql::detail::channel::ssl_handshake( error_code& ec ) { - create_ssl_block(); - ssl_block_->stream.handshake(boost::asio::ssl::stream_base::client, ec); + create_ssl_stream(); + ssl_stream_->handshake(boost::asio::ssl::stream_base::client, ec); } template @@ -419,8 +434,8 @@ boost::mysql::detail::channel::async_ssl_handshake( CompletionToken&& token ) { - create_ssl_block(); - return ssl_block_->stream.async_handshake( + create_ssl_stream(); + return ssl_stream_->async_handshake( boost::asio::ssl::stream_base::client, std::forward(token) ); diff --git a/include/boost/mysql/impl/prepared_statement.hpp b/include/boost/mysql/impl/prepared_statement.hpp index 0187f9c0..b5c81950 100644 --- a/include/boost/mysql/impl/prepared_statement.hpp +++ b/include/boost/mysql/impl/prepared_statement.hpp @@ -13,6 +13,7 @@ #include "boost/mysql/detail/auxiliar/stringize.hpp" #include + template template void boost::mysql::prepared_statement::check_num_params( diff --git a/include/boost/mysql/impl/value.hpp b/include/boost/mysql/impl/value.hpp index 36f911b2..0bd11666 100644 --- a/include/boost/mysql/impl/value.hpp +++ b/include/boost/mysql/impl/value.hpp @@ -126,6 +126,19 @@ BOOST_CXX14_CONSTEXPR Optional get_optional_impl( return res ? Optional(*res) : value_converter::convert(val); } +// Helper class to implement is_convertible_to +// We can't use a boost::optional because it's not a literal type, +// and std::optional is only available in C++17 +template +class is_convertible_to_helper +{ + bool has_value_ {false}; +public: + constexpr is_convertible_to_helper() = default; + constexpr is_convertible_to_helper(const T&) noexcept : has_value_(true) {} + constexpr bool has_value() const noexcept { return has_value_; } +}; + } // detail } // mysql } // boost @@ -149,6 +162,12 @@ BOOST_CXX14_CONSTEXPR boost::mysql::value::value( { } +template +BOOST_CXX14_CONSTEXPR bool boost::mysql::value::is_convertible_to() const noexcept +{ + return detail::get_optional_impl(repr_).has_value(); +} + template boost::optional boost::mysql::value::get_optional() const noexcept { @@ -168,7 +187,7 @@ T boost::mysql::value::get() const { auto res = get_optional(); if (!res) - throw boost::variant2::bad_variant_access(); + throw bad_value_access(); return *res; } diff --git a/include/boost/mysql/prepared_statement.hpp b/include/boost/mysql/prepared_statement.hpp index d957619e..0840c90f 100644 --- a/include/boost/mysql/prepared_statement.hpp +++ b/include/boost/mysql/prepared_statement.hpp @@ -30,10 +30,10 @@ constexpr std::array no_statement_params {}; * The main use of a prepared statement is executing it * using [refmem prepared_statement execute], which yields a [reflink resultset]. * - * Prepared statements are default constructible. A default-constructed prepared statement - * is considered invalid ([refmem prepared_statement valid] will return `false`). Calling - * any function other than assignment on an invalid statement results in - * undefined behavior. + * Prepared statements are default-constructible and movable, but not copyable. + * [refmem prepared_statement valid] returns `false` for default-constructed + * and moved-from prepared statements. Calling any member function on an invalid + * prepared statements, other than assignment, results in undefined behavior. * * Prepared statements are managed by the server in a per-connection basis: * once created, a prepared statement may be used as long as the parent @@ -45,7 +45,7 @@ constexpr std::array no_statement_params {}; template class prepared_statement { - detail::channel* channel_ {}; + detail::channel_observer_ptr channel_; detail::com_stmt_prepare_ok_packet stmt_msg_; template @@ -55,11 +55,29 @@ class prepared_statement struct async_execute_initiation; public: - /// Default constructor. Default constructed statements - /// have [refmem prepared_statement valid] return `false`. + /** + * \brief Default constructor. + * \details Default constructed statements have [refmem prepared_statement valid] return `false`. + */ prepared_statement() = default; + /** + * \brief Move constructor. + * \details The constructed statement will be valid if `other` is valid. + * After this operation, `other` is guaranteed to be invalid. + */ + prepared_statement(prepared_statement&& other) = default; + + /** + * \brief Move assignment. + * \details The assigned-to statement will be valid if `other` is valid. + */ + prepared_statement& operator=(prepared_statement&& rhs) = default; + #ifndef BOOST_MYSQL_DOXYGEN + prepared_statement(const prepared_statement&) = delete; + prepared_statement& operator=(const prepared_statement&) = delete; + // Private. Do not use. prepared_statement(detail::channel& chan, const detail::com_stmt_prepare_ok_packet& msg) noexcept: channel_(&chan), stmt_msg_(msg) {} diff --git a/include/boost/mysql/resultset.hpp b/include/boost/mysql/resultset.hpp index f2a7887b..bb0946da 100644 --- a/include/boost/mysql/resultset.hpp +++ b/include/boost/mysql/resultset.hpp @@ -29,10 +29,10 @@ namespace mysql { * that allows reading rows progressively. [link mysql.resultsets This section] * provides an in-depth explanation of the mechanics of this class. * - * Resultsets are default-constructible. A default-constructed resultset has - * [refmem resultset valid] return `false`. Calling any member function on an invalid + * Resultsets are default-constructible and movable, but not copyable. + * [refmem resultset valid] returns `false` for default-constructed + * and moved-from resultsets. Calling any member function on an invalid * resultset, other than assignment, results in undefined behavior. - * Resultsets are movable but not copyable. */ template < class Stream @@ -40,7 +40,7 @@ template < class resultset { detail::deserialize_row_fn deserializer_ {}; - detail::channel* channel_; + detail::channel_observer_ptr channel_; detail::resultset_metadata meta_; detail::bytestring ok_packet_buffer_; detail::ok_packet ok_packet_; @@ -57,7 +57,23 @@ class resultset /// \details Default constructed resultsets have [refmem resultset valid] return `false`. resultset(): channel_(nullptr) {}; + /** + * \brief Move constructor. + * \details The constructed resultset will be valid if `other` is valid. + * After this operation, `other` is guaranteed to be invalid. + */ + resultset(resultset&& other) = default; + + /** + * \brief Move assignment. + * \details The assigned-to resultset will be valid if `other` is valid. + */ + resultset& operator=(resultset&& rhs) = default; + #ifndef BOOST_MYSQL_DOXYGEN + resultset(const resultset&) = delete; + resultset& operator=(const resultset&) = delete; + // Private, do not use resultset(detail::channel& channel, detail::resultset_metadata&& meta, detail::deserialize_row_fn deserializer): @@ -248,7 +264,7 @@ class resultset /** * \brief Returns whether this object represents a valid resultset. - * \details Returns `false` for default-constructed resultsets. + * \details Returns `false` for default-constructed and moved-from resultsets. * Calling any member function on an invalid resultset, * other than assignment, results in undefined behavior. */ diff --git a/include/boost/mysql/value.hpp b/include/boost/mysql/value.hpp index a2715a79..f123f473 100644 --- a/include/boost/mysql/value.hpp +++ b/include/boost/mysql/value.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #ifndef BOOST_NO_CXX17_HDR_OPTIONAL @@ -41,6 +42,13 @@ using time = std::chrono::microseconds; /// Monostate type representing a NULL value. using null_t = boost::variant2::monostate; +/// Exception type thrown when trying to access a [reflink value] with an incorrect type. +class bad_value_access : public std::exception +{ +public: + const char* what() const noexcept override { return "bad_value_access"; } +}; + /** * \brief Represents a value in the database of any of the allowed types. * See [link mysql.values this section] for more info. @@ -82,7 +90,7 @@ public: * Caution: `value(NULL)` will __NOT__ match this overload. It will try to construct * a `boost::string_view` from a NULL C string, causing undefined behavior. */ - BOOST_CXX14_CONSTEXPR value(std::nullptr_t) noexcept {} + BOOST_CXX14_CONSTEXPR value(std::nullptr_t) noexcept : repr_(null_t()) {} /** * \brief Initialization constructor. @@ -142,10 +150,7 @@ public: * [refmem value get_std_optional] and check the returned optional instead. */ template - BOOST_CXX14_CONSTEXPR bool is_convertible_to() const noexcept - { - return get_optional().has_value(); - } + BOOST_CXX14_CONSTEXPR bool is_convertible_to() const noexcept; /** * \brief Retrieves the stored value or throws an exception. @@ -153,7 +158,7 @@ public: * If the stored value is a `T`, or can be converted to `T` using * one of [link mysql.values.conversions the allowed conversions], * returns the converted value. Otherwise throws - * `boost::variant2::bad_variant_access`. + * [reflink bad_value_access]. */ template T get() const; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 556fe51e..2986bfc2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -29,24 +29,25 @@ target_link_libraries( # Unit testing add_executable( boost_mysql_unittests - # unit/detail/auth/auth_calculator.cpp - # unit/detail/auxiliar/static_string.cpp - # unit/detail/auxiliar/value_type_traits.cpp - # unit/detail/protocol/capabilities.cpp - # unit/detail/protocol/date.cpp - # unit/detail/protocol/null_bitmap_traits.cpp - # unit/detail/protocol/serialization_test.cpp - # unit/detail/protocol/text_deserialization_value.cpp - # unit/detail/protocol/text_deserialization_error.cpp - # unit/detail/protocol/binary_deserialization_value.cpp - # unit/detail/protocol/binary_deserialization_error.cpp - # unit/detail/protocol/row_deserialization.cpp - # unit/metadata.cpp + unit/detail/auth/auth_calculator.cpp + unit/detail/auxiliar/static_string.cpp + unit/detail/auxiliar/value_type_traits.cpp + unit/detail/protocol/capabilities.cpp + unit/detail/protocol/date.cpp + unit/detail/protocol/null_bitmap_traits.cpp + unit/detail/protocol/serialization_test.cpp + unit/detail/protocol/text_deserialization_value.cpp + unit/detail/protocol/text_deserialization_error.cpp + unit/detail/protocol/binary_deserialization_value.cpp + unit/detail/protocol/binary_deserialization_error.cpp + unit/detail/protocol/row_deserialization.cpp + unit/metadata.cpp unit/value.cpp + unit/value_constexpr.cpp unit/row.cpp - # unit/error.cpp - # unit/execute_params.cpp - # unit/prepared_statement.cpp + unit/error.cpp + unit/execute_params.cpp + unit/prepared_statement.cpp unit/resultset.cpp unit/connection.cpp unit/entry_point.cpp diff --git a/test/common/test_stream.hpp b/test/common/test_stream.hpp new file mode 100644 index 00000000..ea1ff69c --- /dev/null +++ b/test/common/test_stream.hpp @@ -0,0 +1,75 @@ +// +// Copyright (c) 2019-2021 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_TEST_COMMON_TEST_STREAM_HPP +#define BOOST_MYSQL_TEST_COMMON_TEST_STREAM_HPP + +#include +#include + +namespace boost { +namespace mysql { +namespace test { + +class test_stream +{ +public: + using executor_type = boost::asio::executor; + + using lowest_layer_type = test_stream; + + lowest_layer_type& lowest_layer() noexcept { return *this; } + + executor_type get_executor() noexcept { return executor_type(); } + + template + std::size_t + read_some(const MutableBufferSequence&) { return 0; } + + template + std::size_t + read_some(const MutableBufferSequence&, boost::mysql::error_code& ec) { ec = {}; return 0; } + + template< + class MutableBufferSequence, + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::size_t)) CompletionToken + BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) + > + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, std::size_t)) + async_read_some( + const MutableBufferSequence&, + CompletionToken&& BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type)) + { + } + + template + std::size_t + write_some(const ConstBufferSequence&) { return 0; } + + template + std::size_t + write_some(const ConstBufferSequence&, error_code& ec) { ec = {}; return 0; } + + template< + class ConstBufferSequence, + BOOST_ASIO_COMPLETION_TOKEN_FOR(void(error_code, std::size_t)) CompletionToken + BOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type) + > + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code, std::size_t)) + async_write_some( + const ConstBufferSequence&, + CompletionToken&& BOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type) + ) + { + } +}; + +} +} +} + +#endif \ No newline at end of file diff --git a/test/integration/handshake.cpp b/test/integration/handshake.cpp index dd1378f4..42f0e633 100644 --- a/test/integration/handshake.cpp +++ b/test/integration/handshake.cpp @@ -6,21 +6,23 @@ // #include "boost/mysql/connection.hpp" +#include "boost/mysql/connection_params.hpp" #include "integration_test_common.hpp" +#include "network_functions.hpp" #include "test_common.hpp" +#include +#include +#include +#include // Tests containing with label 'sha256' require SHA256 functionality. // This is used by the build script to exclude these tests on databases // that do not support this functionality -namespace net = boost::asio; using namespace boost::mysql::test; using boost::mysql::socket_connection; using boost::mysql::ssl_mode; -using boost::mysql::ssl_options; -using boost::mysql::detail::make_error_code; -using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::error_code; using boost::mysql::tcp_connection; @@ -36,7 +38,7 @@ network_result do_handshake( ssl_mode ssl ) { - params.set_ssl(boost::mysql::ssl_options(ssl)); + params.set_ssl(ssl); return net->handshake(conn, params); } @@ -199,6 +201,83 @@ BOOST_MYSQL_NETWORK_TEST(bad_password_ssl_on_cache_miss, caching_sha2_fixture, n BOOST_AUTO_TEST_SUITE_END() // caching_sha2_password +// Externally-provided SSL context tests +BOOST_AUTO_TEST_SUITE(external_ssl_context) + +template +struct external_ssl_context_fixture : public network_fixture +{ + external_ssl_context_fixture(): network_fixture(use_external_ctx) {} +}; + +// The CA file that signed the server's certificate +constexpr const char CA_PEM [] = R"%(-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIUWznm2UoxXw3j7HCcp9PpiayTvFQwDQYJKoZIhvcNAQEL +BQAwQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM +BW15c3FsMQ4wDAYDVQQDDAVteXNxbDAgFw0yMDA0MDQxNDMwMjNaGA8zMDE5MDgw +NjE0MzAyM1owQjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAM +BgNVBAoMBW15c3FsMQ4wDAYDVQQDDAVteXNxbDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAN0WYdvsDb+a0TxOGPejcwZT0zvTrf921mmDUlrLN1Z0hJ/S +ydgQCSD7Q+6za4lTFZCXcvs52xvvS2gfC0yXyYLCT/jA4RQRxuF+/+w1gDWEbGk0 +KzEpsBuKrEIvEaVdoS78SxInnW/aegshdrRRocp4JQ6KHsZgkLTxSwPfYSUmMUo0 +cRO0Q/ak3VK8NP13A6ZFvZjrBxjS3cSw9HqilgADcyj1D4EokvfI1C9LrgwgLlZC +XVkjjBqqoMXGGlnXOEK+pm8bU68HM/QvMBkb1Amo8pioNaaYgqJUCP0Ch0iu1nUU +HtsWt6emXv0jANgIW0oga7xcT4MDGN/M+IRWLTECAwEAAaNTMFEwHQYDVR0OBBYE +FNxhaGwf5ePPhzK7yOAKD3VF6wm2MB8GA1UdIwQYMBaAFNxhaGwf5ePPhzK7yOAK +D3VF6wm2MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAoeJCAX +IDCFoAaZoQ1niI6Ac/cds8G8It0UCcFGSg+HrZ0YujJxWIruRCUG60Q2OAbEvn0+ +uRpTm+4tV1Wt92WFeuRyqkomozx0g4CyfsxGX/x8mLhKPFK/7K9iTXM4/t+xQC4f +J+iRmPVsMKQ8YsHYiWVhlOMH9XJQiqERCB2kOKJCH6xkaF2k0GbM2sGgbS7Z6lrd +fsFTOIVx0VxLVsZnWX3byE9ghnDR5jn18u30Cpb/R/ShxNUGIHqRa4DkM5la6uZX +W1fpSW11JBSUv4WnOO0C2rlIu7UJWOROqZZ0OsybPRGGwagcyff2qVRuI2XFvAMk +OzBrmpfHEhF6NDU= +-----END CERTIFICATE----- +)%"; + +BOOST_MYSQL_NETWORK_TEST(certificate_valid, external_ssl_context_fixture, network_gen) +{ + this->external_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + this->external_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + this->physical_connect(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} + +BOOST_MYSQL_NETWORK_TEST(certificate_invalid, external_ssl_context_fixture, network_gen) +{ + this->external_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + this->physical_connect(); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::require); + BOOST_TEST(result.err.message() == "certificate verify failed"); +} + +BOOST_MYSQL_NETWORK_TEST(custom_certificate_verification_failed, external_ssl_context_fixture, network_gen) +{ + this->external_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + this->external_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + this->external_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("host.name")); + this->physical_connect(); + auto result = do_handshake(this->conn, this->params, sample.net, ssl_mode::require); + BOOST_TEST(result.err.message() == "certificate verify failed"); +} + +BOOST_MYSQL_NETWORK_TEST(custom_certificate_verification_ok, external_ssl_context_fixture, network_gen) +{ + this->external_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + this->external_ctx.add_certificate_authority(boost::asio::buffer(CA_PEM)); + this->external_ctx.set_verify_callback(boost::asio::ssl::host_name_verification("mysql")); + this->physical_connect(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::require); +} + +BOOST_MYSQL_NETWORK_TEST(ssl_disable, external_ssl_context_fixture, network_gen) +{ + this->external_ctx.set_verify_mode(boost::asio::ssl::verify_peer); // should have no effect + this->physical_connect(); + do_handshake_ok(this->conn, this->params, sample.net, ssl_mode::disable); +} + +BOOST_AUTO_TEST_SUITE_END() // external_ssl_context + // Other handshake tests BOOST_MYSQL_NETWORK_TEST(no_database, handshake_fixture, network_ssl_gen) { diff --git a/test/integration/integration_test_common.hpp b/test/integration/integration_test_common.hpp index 987f50f9..db9aff27 100644 --- a/test/integration/integration_test_common.hpp +++ b/test/integration/integration_test_common.hpp @@ -10,6 +10,7 @@ #include "boost/mysql/connection.hpp" #include +#include #include #include #include "test_common.hpp" @@ -32,6 +33,9 @@ void validate_ssl(const connection& conn, ssl_mode m) BOOST_TEST(conn.uses_ssl() == should_use_ssl); } +struct use_external_ctx_t {}; +constexpr use_external_ctx_t use_external_ctx {}; + /** * Base fixture to use in integration tests. The fixture constructor creates * a connection, an asio io_context and a thread to run it. @@ -43,6 +47,7 @@ struct network_fixture { using stream_type = Stream; + boost::asio::ssl::context external_ctx {boost::asio::ssl::context::tls_client}; // for external ctx tests connection_params params; boost::asio::io_context ctx; socket_connection conn; @@ -57,6 +62,14 @@ struct network_fixture { } + network_fixture(use_external_ctx_t) : + params("integ_user", "integ_password", "boost_mysql_integtests"), + conn(external_ctx, ctx.get_executor()), + guard(ctx.get_executor()), + runner([this] { ctx.run(); }) + { + } + ~network_fixture() { error_code code; @@ -79,7 +92,7 @@ struct network_fixture void handshake(ssl_mode m = ssl_mode::require) { - params.set_ssl(ssl_options(m)); + params.set_ssl(m); conn.handshake(params); validate_ssl(conn, m); } diff --git a/test/integration/network_functions/network_functions_impl.cpp b/test/integration/network_functions/network_functions_impl.cpp index 516a5396..cbd58693 100644 --- a/test/integration/network_functions/network_functions_impl.cpp +++ b/test/integration/network_functions/network_functions_impl.cpp @@ -25,7 +25,7 @@ static const char* get_message( void network_result_base::validate_no_error() const { BOOST_TEST_REQUIRE(err == error_code(), - "with error_info= " << get_message(info)); + "with error_info= " << get_message(info) << ", error_code=" << err.message()); if (info) { BOOST_TEST(*info == error_info()); diff --git a/test/integration/prepare_statement.cpp b/test/integration/prepare_statement.cpp index 107d456c..47e152cc 100644 --- a/test/integration/prepare_statement.cpp +++ b/test/integration/prepare_statement.cpp @@ -5,7 +5,9 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "boost/mysql/prepared_statement.hpp" #include "integration_test_common.hpp" +#include using namespace boost::mysql::test; using boost::mysql::error_code; @@ -13,6 +15,10 @@ using boost::mysql::error_info; using boost::mysql::errc; using boost::mysql::prepared_statement; using boost::mysql::connection; +using boost::asio::ip::tcp; +using boost::mysql::tcp_prepared_statement; +using boost::mysql::make_values; +using boost::mysql::no_statement_params; BOOST_AUTO_TEST_SUITE(test_prepare_statement) @@ -47,4 +53,59 @@ BOOST_MYSQL_NETWORK_TEST(invalid_statement, network_fixture, network_ssl_gen) BOOST_TEST(!stmt.value.valid()); } +// Move operations +BOOST_AUTO_TEST_SUITE(move_operations) + +BOOST_FIXTURE_TEST_CASE(move_ctor, network_fixture) +{ + // Get a valid prepared statement and perform a move construction + this->connect(boost::mysql::ssl_mode::disable); + tcp_prepared_statement s1 = this->conn.prepare_statement("SELECT * FROM empty_table"); + tcp_prepared_statement s2 (std::move(s1)); + + // Validate valid() + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + + // We can use the 2nd stmt + auto rows = s2.execute(no_statement_params).read_all(); + BOOST_TEST(rows.empty()); +} + +BOOST_FIXTURE_TEST_CASE(move_assignment_to_invalid, network_fixture) +{ + // Get a valid resultset and perform a move assignment + this->connect(boost::mysql::ssl_mode::disable); + tcp_prepared_statement s1 = this->conn.prepare_statement("SELECT * FROM empty_table"); + tcp_prepared_statement s2; + s2 = std::move(s1); + + // Validate valid() + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + + // We can use the 2nd stmt + auto rows = s2.execute(no_statement_params).read_all(); + BOOST_TEST(rows.empty()); +} + +BOOST_FIXTURE_TEST_CASE(move_assignment_to_valid, network_fixture) +{ + // Get a valid resultset and perform a move assignment + this->connect(boost::mysql::ssl_mode::disable); + tcp_prepared_statement s1 = this->conn.prepare_statement("SELECT * FROM empty_table"); + tcp_prepared_statement s2 = this->conn.prepare_statement("SELECT * FROM empty_table WHERE id IN (?, ?)"); + s2 = std::move(s1); + + // Validate valid() + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + + // We can use the 2nd resultset + auto rows = s2.execute(no_statement_params).read_all(); + BOOST_TEST(rows.empty()); +} + +BOOST_AUTO_TEST_SUITE_END() // move_operations + BOOST_AUTO_TEST_SUITE_END() // test_prepare_statement diff --git a/test/integration/resultset.cpp b/test/integration/resultset.cpp index 2c577c68..64f11a2d 100644 --- a/test/integration/resultset.cpp +++ b/test/integration/resultset.cpp @@ -5,9 +5,12 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "boost/mysql/resultset.hpp" #include "boost/mysql/row.hpp" #include "integration_test_common.hpp" #include "test_common.hpp" +#include +#include using namespace boost::mysql::test; using boost::mysql::error_code; @@ -15,6 +18,8 @@ using boost::mysql::ssl_mode; using boost::mysql::connection; using boost::mysql::resultset; using boost::mysql::row; +using boost::asio::ip::tcp; +using boost::mysql::tcp_resultset; BOOST_AUTO_TEST_SUITE(test_resultset) @@ -393,5 +398,64 @@ BOOST_MYSQL_NETWORK_TEST(several_rows, network_fixture, sample_gen) BOOST_AUTO_TEST_SUITE_END() // read_all +// Move operations +BOOST_AUTO_TEST_SUITE(move_operations) + +BOOST_FIXTURE_TEST_CASE(move_ctor, network_fixture) +{ + // Get a valid resultset and perform a move construction + this->connect(boost::mysql::ssl_mode::disable); + tcp_resultset r = this->conn.query("SELECT * FROM one_row_table"); + tcp_resultset r2 (std::move(r)); + + // Validate valid() + BOOST_TEST(!r.valid()); + BOOST_TEST(r2.valid()); + + // We can use the 2nd resultset + auto rows = r2.read_all(); + BOOST_TEST((rows == makerows(2, 1, "f0"))); + BOOST_TEST(r2.complete()); +} + +BOOST_FIXTURE_TEST_CASE(move_assignment_to_invalid, network_fixture) +{ + // Get a valid resultset and perform a move assignment + this->connect(boost::mysql::ssl_mode::disable); + tcp_resultset r = this->conn.query("SELECT * FROM one_row_table"); + tcp_resultset r2; + r2 = std::move(r); + + // Validate valid() + BOOST_TEST(!r.valid()); + BOOST_TEST(r2.valid()); + + // We can use the 2nd resultset + auto rows = r2.read_all(); + BOOST_TEST((rows == makerows(2, 1, "f0"))); + BOOST_TEST(r2.complete()); +} + +BOOST_FIXTURE_TEST_CASE(move_assignment_to_valid, network_fixture) +{ + // Get a valid resultset and perform a move assignment + this->connect(boost::mysql::ssl_mode::disable); + tcp_resultset r2 = this->conn.query("SELECT * FROM empty_table"); + r2.read_all(); // clean any remaining packets + tcp_resultset r = this->conn.query("SELECT * FROM one_row_table"); + r2 = std::move(r); + + // Validate valid() + BOOST_TEST(!r.valid()); + BOOST_TEST(r2.valid()); + + // We can use the 2nd resultset + auto rows = r2.read_all(); + BOOST_TEST((rows == makerows(2, 1, "f0"))); + BOOST_TEST(r2.complete()); +} + +BOOST_AUTO_TEST_SUITE_END() // move_operations + BOOST_AUTO_TEST_SUITE_END() // test_resultset diff --git a/test/unit/connection.cpp b/test/unit/connection.cpp index bed0bc44..f38fe2cd 100644 --- a/test/unit/connection.cpp +++ b/test/unit/connection.cpp @@ -6,11 +6,80 @@ // #include "boost/mysql/connection.hpp" +#include "test_stream.hpp" #include #include +using conn_t = boost::mysql::connection; + BOOST_AUTO_TEST_SUITE(test_connection) +// init ctor +BOOST_AUTO_TEST_CASE(init_ctor) +{ + conn_t c; + BOOST_TEST(c.valid()); +} + +// move ctor +BOOST_AUTO_TEST_CASE(move_ctor_from_invalid) +{ + conn_t c1; + conn_t c2 {std::move(c1)}; + conn_t c3 {std::move(c1)}; + BOOST_TEST(!c1.valid()); + BOOST_TEST(!c3.valid()); +} + +BOOST_AUTO_TEST_CASE(move_ctor_from_valid) +{ + conn_t c1; + conn_t c2 {std::move(c1)}; + BOOST_TEST(!c1.valid()); + BOOST_TEST(c2.valid()); +} + +// move assign +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_invalid) +{ + conn_t c1; + conn_t c2 {std::move(c1)}; + conn_t c3; + conn_t c4 {std::move(c2)}; + c3 = std::move(c1); + BOOST_TEST(!c1.valid()); + BOOST_TEST(!c3.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_valid) +{ + conn_t c1; + conn_t c2 {std::move(c1)}; + conn_t c3; + c1 = std::move(c3); + BOOST_TEST(c1.valid()); + BOOST_TEST(!c3.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_valid_to_invalid) +{ + conn_t c1; + conn_t c2 {std::move(c1)}; + conn_t c3; + c3 = std::move(c1); + BOOST_TEST(!c1.valid()); + BOOST_TEST(!c3.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_valid_to_valid) +{ + conn_t c1; + conn_t c2; + c1 = std::move(c2); + BOOST_TEST(c1.valid()); + BOOST_TEST(!c2.valid()); +} + using other_executor = boost::asio::strand; BOOST_AUTO_TEST_CASE(connection_rebind_executor) diff --git a/test/unit/prepared_statement.cpp b/test/unit/prepared_statement.cpp index dadb1fd5..ad472aaa 100644 --- a/test/unit/prepared_statement.cpp +++ b/test/unit/prepared_statement.cpp @@ -5,91 +5,108 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "boost/mysql/detail/protocol/channel.hpp" #include "test_common.hpp" +#include "test_stream.hpp" #include "boost/mysql/prepared_statement.hpp" #include #include +#include using namespace boost::mysql::detail; using boost::mysql::prepared_statement; -using boost::mysql::tcp_prepared_statement; -using boost::asio::ip::tcp; + +using chan_t = boost::mysql::detail::channel; +using stmt_t = prepared_statement; BOOST_AUTO_TEST_SUITE(test_prepared_statement) -struct prepared_statement_fixture +// default ctor +BOOST_AUTO_TEST_CASE(default_ctor) { - boost::asio::io_context ctx; - channel chan {ctx}; -}; - -BOOST_FIXTURE_TEST_CASE(default_constructor, prepared_statement_fixture) -{ - tcp_prepared_statement stmt; - BOOST_TEST(!stmt.valid()); + stmt_t s; + BOOST_TEST(!s.valid()); } -BOOST_FIXTURE_TEST_CASE(initializing_constructor, prepared_statement_fixture) +// init ctor +BOOST_AUTO_TEST_CASE(init_ctor) { - tcp_prepared_statement stmt (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); - BOOST_TEST(stmt.valid()); - BOOST_TEST(stmt.id() == 10); - BOOST_TEST(stmt.num_params() == 8); + chan_t chan; + stmt_t s (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); + BOOST_TEST(s.valid()); + BOOST_TEST(s.id() == 10); + BOOST_TEST(s.num_params() == 8); } -BOOST_FIXTURE_TEST_CASE(move_constructor_from_default_constructed, prepared_statement_fixture) +// move ctor +BOOST_AUTO_TEST_CASE(move_ctor_from_invalid) { - tcp_prepared_statement stmt {tcp_prepared_statement()}; - BOOST_TEST(!stmt.valid()); + stmt_t s1; + stmt_t s2 {std::move(s1)}; + BOOST_TEST(!s1.valid()); + BOOST_TEST(!s2.valid()); } -BOOST_FIXTURE_TEST_CASE(move_constructor_from_valid, prepared_statement_fixture) +BOOST_AUTO_TEST_CASE(move_ctor_from_valid) { - - tcp_prepared_statement stmt (tcp_prepared_statement( - chan, com_stmt_prepare_ok_packet{10, 9, 8, 7} - )); - BOOST_TEST(stmt.valid()); - BOOST_TEST(stmt.id() == 10); - BOOST_TEST(stmt.num_params() == 8); + chan_t chan; + stmt_t s1 (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); + stmt_t s2 (std::move(s1)); + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + BOOST_TEST(s2.id() == 10); + BOOST_TEST(s2.num_params() == 8); } -BOOST_FIXTURE_TEST_CASE(move_assignment_from_default_constructed, prepared_statement_fixture) +// move assign +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_invalid) { - tcp_prepared_statement stmt ( - chan, - com_stmt_prepare_ok_packet{10, 9, 8, 7} - ); - stmt = tcp_prepared_statement(); - BOOST_TEST(!stmt.valid()); - stmt = tcp_prepared_statement(); - BOOST_TEST(!stmt.valid()); + stmt_t s1; + stmt_t s2; + s2 = std::move(s1); + BOOST_TEST(!s1.valid()); + BOOST_TEST(!s2.valid()); } -BOOST_FIXTURE_TEST_CASE(move_assignment_from_valid, prepared_statement_fixture) +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_valid) { - - tcp_prepared_statement stmt; - stmt = tcp_prepared_statement ( - chan, - com_stmt_prepare_ok_packet{10, 9, 8, 7} - ); - BOOST_TEST(stmt.valid()); - BOOST_TEST(stmt.id() == 10); - BOOST_TEST(stmt.num_params() == 8); - stmt = tcp_prepared_statement( - chan, - com_stmt_prepare_ok_packet{1, 2, 3, 4} - ); - BOOST_TEST(stmt.valid()); - BOOST_TEST(stmt.id() == 1); - BOOST_TEST(stmt.num_params() == 3); + chan_t chan; + stmt_t s1; + stmt_t s2 (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); + s2 = std::move(s1); + BOOST_TEST(!s1.valid()); + BOOST_TEST(!s2.valid()); } +BOOST_AUTO_TEST_CASE(move_assign_valid_to_invalid) +{ + chan_t chan; + stmt_t s1 (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); + stmt_t s2; + s2 = std::move(s1); + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + BOOST_TEST(s2.id() == 10); + BOOST_TEST(s2.num_params() == 8); +} + +BOOST_AUTO_TEST_CASE(move_assign_valid_to_valid) +{ + chan_t chan; + stmt_t s1 (chan, com_stmt_prepare_ok_packet{10, 9, 8, 7}); + stmt_t s2 (chan, com_stmt_prepare_ok_packet{1, 2, 3, 4}); + s2 = std::move(s1); + BOOST_TEST(!s1.valid()); + BOOST_TEST(s2.valid()); + BOOST_TEST(s2.id() == 10); + BOOST_TEST(s2.num_params() == 8); +} + +// rebind executor BOOST_AUTO_TEST_CASE(rebind_executor) { using other_executor = boost::asio::strand; - using rebound_type = tcp_prepared_statement::rebind_executor::other; + using rebound_type = boost::mysql::tcp_prepared_statement::rebind_executor::other; using expected_type = boost::mysql::prepared_statement< boost::asio::basic_stream_socket< boost::asio::ip::tcp, diff --git a/test/unit/resultset.cpp b/test/unit/resultset.cpp index c8671e41..76ab5cc7 100644 --- a/test/unit/resultset.cpp +++ b/test/unit/resultset.cpp @@ -6,11 +6,83 @@ // #include "boost/mysql/resultset.hpp" +#include "boost/mysql/detail/protocol/channel.hpp" +#include #include #include +#include "test_stream.hpp" + +using resultset_t = boost::mysql::resultset; +using chan_t = boost::mysql::detail::channel; BOOST_AUTO_TEST_SUITE(test_resultset) +// default ctor +BOOST_AUTO_TEST_CASE(default_ctor) +{ + resultset_t r; + BOOST_TEST(!r.valid()); +} + +// move ctor +BOOST_AUTO_TEST_CASE(move_ctor_from_invalid) +{ + resultset_t r1; + resultset_t r2 (std::move(r1)); + BOOST_TEST(!r1.valid()); + BOOST_TEST(!r2.valid()); +} + +BOOST_AUTO_TEST_CASE(move_ctor_from_valid) +{ + chan_t chan; + resultset_t r1 (chan, {}, {}); + resultset_t r2 (std::move(r1)); + BOOST_TEST(!r1.valid()); + BOOST_TEST(r2.valid()); +} + +// move assignment +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_invalid) +{ + resultset_t r1; + resultset_t r2; + r2 = std::move(r1); + BOOST_TEST(!r1.valid()); + BOOST_TEST(!r2.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_invalid_to_valid) +{ + chan_t chan; + resultset_t r1; + resultset_t r2 (chan, {}, {}); + r2 = std::move(r1); + BOOST_TEST(!r1.valid()); + BOOST_TEST(!r2.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_valid_to_invalid) +{ + chan_t chan; + resultset_t r1 (chan, {}, {}); + resultset_t r2; + r2 = std::move(r1); + BOOST_TEST(!r1.valid()); + BOOST_TEST(r2.valid()); +} + +BOOST_AUTO_TEST_CASE(move_assign_valid_to_valid) +{ + chan_t chan; + resultset_t r1 (chan, {}, {}); + resultset_t r2 (chan, {}, {}); + r2 = std::move(r1); + BOOST_TEST(!r1.valid()); + BOOST_TEST(r2.valid()); +} + +// rebind executor BOOST_AUTO_TEST_CASE(rebind_executor) { using other_executor = boost::asio::strand; diff --git a/test/unit/value.cpp b/test/unit/value.cpp index 13876452..28afbb52 100644 --- a/test/unit/value.cpp +++ b/test/unit/value.cpp @@ -284,7 +284,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(get, T, all_types) } else { - BOOST_CHECK_THROW(sample.v.get(), boost::variant2::bad_variant_access); + BOOST_CHECK_THROW(sample.v.get(), boost::mysql::bad_value_access); } }); } diff --git a/test/unit/value_constexpr.cpp b/test/unit/value_constexpr.cpp new file mode 100644 index 00000000..60d95603 --- /dev/null +++ b/test/unit/value_constexpr.cpp @@ -0,0 +1,208 @@ +// +// Copyright (c) 2019-2021 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include "boost/mysql/value.hpp" +#include "test_common.hpp" +#include +#include +#include + +using boost::mysql::value; +using boost::mysql::null_t; +using boost::mysql::datetime; +using boost::mysql::date; +using boost::mysql::days; +using vt = boost::mysql::value::variant_type; + +#ifndef BOOST_NO_CXX14_CONSTEXPR + +BOOST_AUTO_TEST_SUITE(test_value_constexpr) + +#ifndef _MSC_VER // for some reason, MSVC doesn't like these tests + +BOOST_AUTO_TEST_CASE(null_type) +{ + constexpr value v; + constexpr value v2 (nullptr); + static_assert(v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(null_t()), ""); + static_assert(v == v2, ""); + static_assert(v != value(10), ""); + static_assert(v < value(10), ""); + static_assert(v <= value(10), ""); + static_assert(v >= v2, ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +#endif + +BOOST_AUTO_TEST_CASE(int64_t_type) +{ + constexpr value v (std::int64_t(60)); + constexpr value v2 (-4); // from other int type + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(std::int64_t(60)), ""); + static_assert(v == v, ""); + static_assert(v != v2, ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(uint64_t_type) +{ + constexpr value v (std::uint64_t(60)); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(std::uint64_t(60)), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(string_view_type) +{ + constexpr value v (boost::string_view("test", 4)); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + // Equality is not constexpr for strings + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(float_type) +{ + constexpr value v (3.14f); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(3.14f), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(double_type) +{ + constexpr value v (3.14); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(3.14), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(date_type) +{ + constexpr date d (days(1)); + constexpr value v (d); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(d), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(datetime_type) +{ + constexpr datetime d (days(1)); + constexpr value v (d); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(d), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_CASE(time_type) +{ + constexpr boost::mysql::time t (1); + constexpr value v (t); + static_assert(!v.is_null(), ""); + static_assert(v.is(), ""); + static_assert(v.is_convertible_to(), ""); + static_assert(v.to_variant() == vt(t), ""); + static_assert(v == v, ""); + static_assert(v != value(std::int64_t(0)), ""); + static_assert(v < value(boost::mysql::time(9999)), ""); + static_assert(v <= value(boost::mysql::time(9999)), ""); + static_assert(v > value(std::int64_t(0)), ""); + static_assert(v >= value(std::int64_t(0)), ""); + + #ifndef BOOST_NO_CXX17_HDR_OPTIONAL + static_assert(v.get_std_optional(), ""); + #endif +} + +BOOST_AUTO_TEST_SUITE_END() + +#endif \ No newline at end of file diff --git a/tools/build_docs.sh b/tools/build_docs.sh index b0085a26..e4d275fe 100644 --- a/tools/build_docs.sh +++ b/tools/build_docs.sh @@ -1,4 +1,10 @@ #!/bin/bash +# +# Copyright (c) 2019-2021 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# MYSQL_LIB_DIR=$(pwd) BUILD_DIR=$HOME/build diff --git a/tools/build_unix.sh b/tools/build_unix.sh index f1db99d9..87f5ab3a 100644 --- a/tools/build_unix.sh +++ b/tools/build_unix.sh @@ -55,6 +55,8 @@ function setup_db { exit 1 fi cp tools/osx-ci.cnf ~/.my.cnf + sudo mkdir -p /etc/ssl/certs/mysql/ + sudo cp tools/ssl/*.pem /etc/ssl/certs/mysql/ mysql.server start # Note that running this with sudo fails mysql -u root < test/integration/db_setup.sql mysql -u root < test/integration/db_setup_sha256.sql diff --git a/tools/docker/mariadb10.3.dockerfile b/tools/docker/mariadb10.3.dockerfile index d3bbbf77..219d3b7d 100644 --- a/tools/docker/mariadb10.3.dockerfile +++ b/tools/docker/mariadb10.3.dockerfile @@ -13,7 +13,7 @@ ENV MYSQL_ROOT_PASSWORD= COPY example/db_setup.sql /docker-entrypoint-initdb.d/example_db_setup.sql COPY test/integration/db_setup.sql /docker-entrypoint-initdb.d/ COPY tools/docker/mysql_entrypoint.sh / -COPY tools/docker/*.pem /etc/ssl/certs/mysql/ -COPY tools/docker/mariadb10.3.ssl.cnf /etc/mysql/conf.d/ +COPY tools/docker/ssl.cnf /etc/mysql/conf.d/ +COPY tools/ssl/*.pem /etc/ssl/certs/mysql/ ENTRYPOINT ["/bin/bash", "/mysql_entrypoint.sh"] \ No newline at end of file diff --git a/tools/docker/mysql5.dockerfile b/tools/docker/mysql5.dockerfile index 497085b1..6fef59b8 100644 --- a/tools/docker/mysql5.dockerfile +++ b/tools/docker/mysql5.dockerfile @@ -13,5 +13,7 @@ ENV MYSQL_ROOT_PASSWORD= COPY example/db_setup.sql /docker-entrypoint-initdb.d/example_db_setup.sql COPY test/integration/db_setup.sql /docker-entrypoint-initdb.d/ COPY tools/docker/mysql_entrypoint.sh / +COPY tools/docker/ssl.cnf /etc/mysql/conf.d/ +COPY tools/ssl/*.pem /etc/ssl/certs/mysql/ ENTRYPOINT ["/bin/bash", "/mysql_entrypoint.sh"] \ No newline at end of file diff --git a/tools/docker/mysql8.dockerfile b/tools/docker/mysql8.dockerfile index 3724aa99..aba0b749 100644 --- a/tools/docker/mysql8.dockerfile +++ b/tools/docker/mysql8.dockerfile @@ -13,5 +13,7 @@ ENV MYSQL_ROOT_PASSWORD= COPY example/db_setup.sql /docker-entrypoint-initdb.d/example_db_setup.sql COPY test/integration/*.sql /docker-entrypoint-initdb.d/ COPY tools/docker/mysql_entrypoint.sh / +COPY tools/docker/ssl.cnf /etc/mysql/conf.d/ +COPY tools/ssl/*.pem /etc/ssl/certs/mysql/ ENTRYPOINT ["/bin/bash", "/mysql_entrypoint.sh"] \ No newline at end of file diff --git a/tools/docker/mariadb10.3.ssl.cnf b/tools/docker/ssl.cnf similarity index 100% rename from tools/docker/mariadb10.3.ssl.cnf rename to tools/docker/ssl.cnf diff --git a/tools/file_headers.py b/tools/file_headers.py index 45da0693..1cdd5c6d 100644 --- a/tools/file_headers.py +++ b/tools/file_headers.py @@ -130,7 +130,7 @@ bash_processor = NormalProcessor('bash', gen_header('#', shebang='#!/bin/bash')) bat_processor = NormalProcessor('bat', gen_header('REM')) FILE_PROCESSORS = [ - ('docca-stage2-noescape.xsl', IgnoreProcessor()), + ('docca-base-stage2-noescape.xsl', IgnoreProcessor()), ('CMakeLists.txt', hash_processor), ('.cmake', hash_processor), ('.cmake.in', hash_processor), diff --git a/tools/osx-ci.cnf b/tools/osx-ci.cnf index 06a9f418..57ca84d5 100644 --- a/tools/osx-ci.cnf +++ b/tools/osx-ci.cnf @@ -7,6 +7,9 @@ [mysqld] socket=/var/run/mysqld/mysqld.sock +ssl-ca=/etc/ssl/certs/mysql/ca-cert.pem +ssl-cert=/etc/ssl/certs/mysql/server-cert.pem +ssl-key=/etc/ssl/certs/mysql/server-key.pem [client] socket=/var/run/mysqld/mysqld.sock \ No newline at end of file diff --git a/tools/docker/ca-cert.pem b/tools/ssl/ca-cert.pem similarity index 100% rename from tools/docker/ca-cert.pem rename to tools/ssl/ca-cert.pem diff --git a/tools/docker/server-cert.pem b/tools/ssl/server-cert.pem similarity index 100% rename from tools/docker/server-cert.pem rename to tools/ssl/server-cert.pem diff --git a/tools/docker/server-key.pem b/tools/ssl/server-key.pem similarity index 100% rename from tools/docker/server-key.pem rename to tools/ssl/server-key.pem