diff --git a/TODO.txt b/TODO.txt index 34f2b973..35651100 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,14 +1,12 @@ Sanitize Test zero dates - Connection quit - Base class for async stuff: make it usable for channel Connection connect Close/quit to channel Integration test for network errors (e.g. host unreachable) Class with an error info, error code and a check method Change read_row_result async signature to use error_info* - Attempt to remove shared_ptr's from ops Replace yields by asio macros + Simplify channel Better docs Wandbox Breaking up the tutorial in pieces diff --git a/include/boost/mysql/detail/network_algorithms/async_op.hpp b/include/boost/mysql/detail/network_algorithms/async_op.hpp deleted file mode 100644 index 666beedf..00000000 --- a/include/boost/mysql/detail/network_algorithms/async_op.hpp +++ /dev/null @@ -1,99 +0,0 @@ -// -// Copyright (c) 2019-2020 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 INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_ASYNC_OP_HPP_ -#define INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_ASYNC_OP_HPP_ - -#include "boost/mysql/detail/protocol/channel.hpp" -#include "boost/mysql/error.hpp" -#include -#include -#include - -namespace boost { -namespace mysql { -namespace detail { - -template < - typename Stream, - typename CompletionToken, - typename HandlerSignature -> -using async_op_base = boost::beast::async_base< - BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature), - typename Stream::executor_type ->; - -template < - typename Stream, - typename CompletionToken, - typename HandlerSignature, - typename Derived // CRTP -> -class async_op : - public boost::asio::coroutine, - public async_op_base -{ - channel& channel_; - error_info* output_info_; - - using base_type = async_op_base; -public: - using async_completion_type = boost::asio::async_completion; - - async_op(async_completion_type& initiator, channel& chan, error_info* output_info) : - base_type(std::move(initiator.completion_handler), chan.next_layer().get_executor()), - channel_(chan), - output_info_(output_info) - { - } - - channel& get_channel() noexcept { return channel_; } - error_info* get_output_info() noexcept { return output_info_; } - - template - static auto initiate( - CompletionToken&& token, - channel& chan, - error_info* output_info, - Args&&... args - ) - { - async_completion_type completion (token); - Derived op (completion, chan, output_info, std::forward(args)...); - op(error_code()); - return completion.result.get(); - } - - // Reads from channel against the channel internal buffer, using itself as - void async_read() { async_read(channel_.shared_buffer()); } - void async_read(bytestring& buff) - { - channel_.async_read( - buff, - std::move(static_cast(*this)) - ); - } - - void async_write(const bytestring& buff) - { - channel_.async_write( - boost::asio::buffer(buff), - std::move(static_cast(*this)) - ); - } - - void async_write() { async_write(channel_.shared_buffer()); } -}; - -} // detail -} // mysql -} // boost - - - -#endif /* INCLUDE_BOOST_MYSQL_DETAIL_NETWORK_ALGORITHMS_ASYNC_OP_HPP_ */ diff --git a/include/boost/mysql/detail/network_algorithms/common.hpp b/include/boost/mysql/detail/network_algorithms/common.hpp index 51c4953e..71796505 100644 --- a/include/boost/mysql/detail/network_algorithms/common.hpp +++ b/include/boost/mysql/detail/network_algorithms/common.hpp @@ -12,7 +12,6 @@ #include "boost/mysql/metadata.hpp" #include "boost/mysql/detail/protocol/channel.hpp" #include "boost/mysql/detail/protocol/common_messages.hpp" -#include "boost/mysql/detail/network_algorithms/async_op.hpp" namespace boost { namespace mysql { diff --git a/include/boost/mysql/detail/protocol/channel.hpp b/include/boost/mysql/detail/protocol/channel.hpp index c1f31de2..5f620a7a 100644 --- a/include/boost/mysql/detail/protocol/channel.hpp +++ b/include/boost/mysql/detail/protocol/channel.hpp @@ -15,8 +15,8 @@ #include #include #include -#include -#include +#include +#include #include #include @@ -24,22 +24,22 @@ namespace boost { namespace mysql { namespace detail { -template +// Implements the message layer of the MySQL protocol +template class channel { - // TODO: static asserts for AsyncStream concept - // TODO: actually we also require it to be SyncStream, name misleading + // TODO: static asserts for Stream concept struct ssl_block { boost::asio::ssl::context ctx; - boost::asio::ssl::stream stream; + boost::asio::ssl::stream stream; - ssl_block(AsyncStream& base_stream): + ssl_block(Stream& base_stream): ctx(boost::asio::ssl::context::tls_client), stream (base_stream, ctx) {} }; - AsyncStream& stream_; + Stream& stream_; std::optional ssl_block_; std::uint8_t sequence_number_ {0}; std::array header_buffer_ {}; // for async ops @@ -66,42 +66,129 @@ class channel template auto async_write_impl(BufferSeq&& buff, CompletionToken&& token); public: - channel(AsyncStream& stream): stream_(stream) {} - - bool ssl_active() const noexcept { return ssl_block_.has_value(); } + channel(Stream& stream): stream_(stream) {} + // Reading template void read(basic_bytestring& buffer, error_code& code); - void write(boost::asio::const_buffer buffer, error_code& code); + using read_signature = void(error_code); template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, read_signature) async_read(basic_bytestring& buffer, CompletionToken&& token); + // Writing + void write(boost::asio::const_buffer buffer, error_code& code); + + using write_signature = void(error_code); + template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, write_signature) async_write(boost::asio::const_buffer buffer, CompletionToken&& token); + // SSL + bool ssl_active() const noexcept { return ssl_block_.has_value(); } + void ssl_handshake(error_code& ec); + using ssl_handshake_signature = void(error_code); + template - BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code)) + BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, ssl_handshake_signature) async_ssl_handshake(CompletionToken&& token); + // Sequence numbers void reset_sequence_number(std::uint8_t value = 0) { sequence_number_ = value; } std::uint8_t sequence_number() const { return sequence_number_; } - using stream_type = AsyncStream; + // Getting the underlying stream + using stream_type = Stream; stream_type& next_layer() { return stream_; } + // Capabilities capabilities current_capabilities() const noexcept { return current_caps_; } void set_current_capabilities(capabilities value) noexcept { current_caps_ = value; } + // Internal buffer const bytestring& shared_buffer() const noexcept { return shared_buff_; } bytestring& shared_buffer() noexcept { return shared_buff_; } }; +template < + typename Stream, + typename CompletionToken, + typename HandlerSignature +> +using async_op_base = boost::beast::async_base< + BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature), + typename Stream::executor_type +>; + +// The base for async operations involving a channel +// Defined here so it can also be used for channel function implementation +template < + typename Stream, + typename CompletionToken, + typename HandlerSignature, + typename Derived // CRTP +> +class async_op : + public boost::asio::coroutine, + public async_op_base +{ + channel& channel_; + error_info* output_info_; + + using base_type = async_op_base; +public: + using async_completion_type = boost::asio::async_completion; + + async_op(async_completion_type& initiator, channel& chan, error_info* output_info) : + base_type(std::move(initiator.completion_handler), chan.next_layer().get_executor()), + channel_(chan), + output_info_(output_info) + { + } + + channel& get_channel() noexcept { return channel_; } + error_info* get_output_info() noexcept { return output_info_; } + + template + static auto initiate( + CompletionToken&& token, + channel& chan, + error_info* output_info, + Args&&... args + ) + { + async_completion_type completion (token); + Derived op (completion, chan, output_info, std::forward(args)...); + op(error_code()); + return completion.result.get(); + } + + // Reads from channel against the channel internal buffer, using itself as + void async_read() { async_read(channel_.shared_buffer()); } + void async_read(bytestring& buff) + { + channel_.async_read( + buff, + std::move(static_cast(*this)) + ); + } + + void async_write(const bytestring& buff) + { + channel_.async_write( + boost::asio::buffer(buff), + std::move(static_cast(*this)) + ); + } + + void async_write() { async_write(channel_.shared_buffer()); } +}; + } // detail } // mysql diff --git a/include/boost/mysql/detail/protocol/impl/channel.hpp b/include/boost/mysql/detail/protocol/impl/channel.hpp index 653754d0..c773c7df 100644 --- a/include/boost/mysql/detail/protocol/impl/channel.hpp +++ b/include/boost/mysql/detail/protocol/impl/channel.hpp @@ -8,7 +8,8 @@ #ifndef BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_CHANNEL_HPP #define BOOST_MYSQL_DETAIL_PROTOCOL_IMPL_CHANNEL_HPP -#include +#include +#include #include #include "boost/mysql/detail/protocol/common_messages.hpp" #include "boost/mysql/detail/protocol/constants.hpp" @@ -34,8 +35,8 @@ inline std::uint32_t compute_size_to_write( } // mysql } // boost -template -bool boost::mysql::detail::channel::process_sequence_number( +template +bool boost::mysql::detail::channel::process_sequence_number( std::uint8_t got ) { @@ -50,8 +51,8 @@ bool boost::mysql::detail::channel::process_sequence_number( } } -template -boost::mysql::error_code boost::mysql::detail::channel::process_header_read( +template +boost::mysql::error_code boost::mysql::detail::channel::process_header_read( std::uint32_t& size_to_read ) { @@ -67,8 +68,8 @@ boost::mysql::error_code boost::mysql::detail::channel::process_hea return error_code(); } -template -void boost::mysql::detail::channel::process_header_write( +template +void boost::mysql::detail::channel::process_header_write( std::uint32_t size_to_write ) { @@ -79,9 +80,9 @@ void boost::mysql::detail::channel::process_header_write( serialize(header, ctx); } -template +template template -std::size_t boost::mysql::detail::channel::read_impl( +std::size_t boost::mysql::detail::channel::read_impl( BufferSeq&& buff, error_code& ec ) @@ -96,9 +97,9 @@ std::size_t boost::mysql::detail::channel::read_impl( } } -template +template template -std::size_t boost::mysql::detail::channel::write_impl( +std::size_t boost::mysql::detail::channel::write_impl( BufferSeq&& buff, error_code& ec ) @@ -113,9 +114,9 @@ std::size_t boost::mysql::detail::channel::write_impl( } } -template +template template -auto boost::mysql::detail::channel::async_read_impl( +auto boost::mysql::detail::channel::async_read_impl( BufferSeq&& buff, CompletionToken&& token ) @@ -138,9 +139,9 @@ auto boost::mysql::detail::channel::async_read_impl( } } -template +template template -auto boost::mysql::detail::channel::async_write_impl( +auto boost::mysql::detail::channel::async_write_impl( BufferSeq&& buff, CompletionToken&& token ) @@ -163,9 +164,9 @@ auto boost::mysql::detail::channel::async_write_impl( } } -template +template template -void boost::mysql::detail::channel::read( +void boost::mysql::detail::channel::read( basic_bytestring& buffer, error_code& code ) @@ -198,8 +199,8 @@ void boost::mysql::detail::channel::read( } while (size_to_read == MAX_PACKET_SIZE); } -template -void boost::mysql::detail::channel::write( +template +void boost::mysql::detail::channel::write( boost::asio::const_buffer buffer, error_code& code ) @@ -227,40 +228,36 @@ void boost::mysql::detail::channel::write( } -template +template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) -boost::mysql::detail::channel::async_read( +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + typename boost::mysql::detail::channel::read_signature +) +boost::mysql::detail::channel::async_read( basic_bytestring& buffer, CompletionToken&& token ) { - using HandlerSignature = void(mysql::error_code); - using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); - using BaseType = boost::beast::async_base; - - boost::asio::async_completion initiator(token); - - struct Op: BaseType, boost::asio::coroutine + struct op : async_op { - channel& stream_; basic_bytestring& buffer_; std::size_t total_transferred_size_ = 0; - Op( - HandlerType&& handler, - channel& stream, + op( + boost::asio::async_completion& completion, + channel& chan, + error_info* output_info, basic_bytestring& buffer - ): - BaseType(std::move(handler), stream.next_layer().get_executor()), - stream_(stream), + ) : + async_op(completion, chan, output_info), buffer_(buffer) { } void operator()( error_code code, - std::size_t bytes_transferred, + std::size_t bytes_transferred=0, bool cont=true ) { @@ -273,17 +270,18 @@ boost::mysql::detail::channel::async_read( // Non-error path std::uint32_t size_to_read = 0; + channel& chan = this->get_channel(); reenter(*this) { do { - yield stream_.async_read_impl( - boost::asio::buffer(stream_.header_buffer_), + yield chan.async_read_impl( + boost::asio::buffer(chan.header_buffer_), std::move(*this) ); - valgrind_make_mem_defined(boost::asio::buffer(stream_.header_buffer_)); + valgrind_make_mem_defined(boost::asio::buffer(chan.header_buffer_)); - code = stream_.process_header_read(size_to_read); + code = chan.process_header_read(size_to_read); if (code) { this->complete(cont, code); @@ -292,7 +290,7 @@ boost::mysql::detail::channel::async_read( buffer_.resize(buffer_.size() + size_to_read); - yield stream_.async_read_impl( + yield chan.async_read_impl( boost::asio::buffer(buffer_.data() + total_transferred_size_, size_to_read), std::move(*this) ); @@ -309,44 +307,39 @@ boost::mysql::detail::channel::async_read( }; buffer.clear(); - Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false); - return initiator.result.get(); + return op::initiate(std::forward(token), *this, nullptr, buffer); } -template +template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) -boost::mysql::detail::channel::async_write( +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + typename boost::mysql::detail::channel::write_signature +) +boost::mysql::detail::channel::async_write( boost::asio::const_buffer buffer, CompletionToken&& token ) { - using HandlerSignature = void(mysql::error_code); - using HandlerType = BOOST_ASIO_HANDLER_TYPE(CompletionToken, HandlerSignature); - using BaseType = boost::beast::async_base; - - boost::asio::async_completion initiator(token); - - struct Op : BaseType, boost::asio::coroutine + struct op : async_op { - channel& stream_; boost::asio::const_buffer buffer_; std::size_t total_transferred_size_ = 0; - Op( - HandlerType&& handler, - channel& stream, + op( + boost::asio::async_completion& completion, + channel& chan, + error_info* output_info, boost::asio::const_buffer buffer - ): - BaseType(std::move(handler), stream.next_layer().get_executor()), - stream_(stream), + ) : + async_op(completion, chan, output_info), buffer_(buffer) { } void operator()( error_code code, - std::size_t bytes_transferred, + std::size_t bytes_transferred=0, bool cont=true ) { @@ -359,17 +352,18 @@ boost::mysql::detail::channel::async_write( // Non-error path std::uint32_t size_to_write; + channel& chan = this->get_channel(); reenter(*this) { // Force write the packet header on an empty packet, at least. do { size_to_write = compute_size_to_write(buffer_.size(), total_transferred_size_); - stream_.process_header_write(size_to_write); + chan.process_header_write(size_to_write); - yield stream_.async_write_impl( + yield chan.async_write_impl( std::array { - boost::asio::buffer(stream_.header_buffer_), + boost::asio::buffer(chan.header_buffer_), boost::asio::buffer(buffer_ + total_transferred_size_, size_to_write) }, std::move(*this) @@ -385,8 +379,7 @@ boost::mysql::detail::channel::async_write( }; - Op(std::move(initiator.completion_handler), *this, buffer)(error_code(), 0, false); - return initiator.result.get(); + return op::initiate(std::forward(token), *this, nullptr, buffer); } template @@ -400,7 +393,10 @@ void boost::mysql::detail::channel::ssl_handshake( template template -BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(boost::mysql::error_code)) +BOOST_ASIO_INITFN_RESULT_TYPE( + CompletionToken, + typename boost::mysql::detail::channel::ssl_handshake_signature +) boost::mysql::detail::channel::async_ssl_handshake( CompletionToken&& token ) @@ -412,6 +408,7 @@ boost::mysql::detail::channel::async_ssl_handshake( ); } + #include