From 79313a61ea0978f34cfa97e99d4fd762f041fa99 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 5 May 2026 04:16:53 -0400 Subject: [PATCH 1/2] Fix deprecated boost API errors in 1.91.0. --- test/config/authority.cpp | 8 ++++---- test/config/utilities.cpp | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/config/authority.cpp b/test/config/authority.cpp index 2a993867a..eeb10b268 100644 --- a/test/config/authority.cpp +++ b/test/config/authority.cpp @@ -223,7 +223,7 @@ BOOST_AUTO_TEST_CASE(authority__ip_port_cidr__ip_address__expected) BOOST_AUTO_TEST_CASE(authority__ip_port_cidr__boost_address__expected) { constexpr uint16_t expected_port = 42; - const auto address = asio::address::from_string(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); + const auto address = boost::asio::ip::make_address(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); const authority host(address, expected_port); BOOST_REQUIRE_EQUAL(host.port(), expected_port); BOOST_REQUIRE_EQUAL(host.ip(), address); @@ -233,7 +233,7 @@ BOOST_AUTO_TEST_CASE(authority__ip_port_cidr__boost_address__expected) BOOST_AUTO_TEST_CASE(authority__port__boost_endpoint__expected) { constexpr uint16_t expected_port = 42; - const auto address = asio::address::from_string(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); + const auto address = boost::asio::ip::make_address(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); const asio::endpoint tcp_endpoint(address, expected_port); const authority host(tcp_endpoint); BOOST_REQUIRE_EQUAL(host.port(), expected_port); @@ -290,14 +290,14 @@ BOOST_AUTO_TEST_CASE(authority__to_ip_address__ip_address__expected) BOOST_AUTO_TEST_CASE(authority__to_ip_address__boost_address__expected) { - const auto address = asio::address::from_string(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); + const auto address = boost::asio::ip::make_address(BC_AUTHORITY_IPV6_COMPRESSED_ADDRESS); const authority host(address, 42); BOOST_REQUIRE(ip_equal(host.to_ip_address(), test_ipv6_address)); } BOOST_AUTO_TEST_CASE(authority__to_ip_address__boost_endpoint__expected) { - const auto address = asio::address::from_string(BC_AUTHORITY_IPV4_ADDRESS); + const auto address = boost::asio::ip::make_address(BC_AUTHORITY_IPV4_ADDRESS); const asio::endpoint tcp_endpoint(address, 42); const authority host(tcp_endpoint); BOOST_REQUIRE(ip_equal(host.to_ip_address(), test_mapped_ip_address)); diff --git a/test/config/utilities.cpp b/test/config/utilities.cpp index 4ea6aa909..8a335caf8 100644 --- a/test/config/utilities.cpp +++ b/test/config/utilities.cpp @@ -382,7 +382,10 @@ BOOST_AUTO_TEST_CASE(utilities__from_address__v6_mapped_loopback__loopback_v4) // address.to_v4() is not the same as ipv6.to_v4(), the former is a getter // while the latter is a convertor. BOOST_REQUIRE(ip.to_v6().is_v4_mapped()); - BOOST_REQUIRE(ip.to_v6().to_v4().is_loopback()); + + ////BOOST_REQUIRE(ip.to_v6().to_v4().is_loopback()); + const auto loop = boost::asio::ip::make_address_v4(boost::asio::ip::v4_mapped, ip.to_v6()); + BOOST_REQUIRE(loop.is_loopback()); } BOOST_AUTO_TEST_CASE(utilities__from_address__loopback_v6__loopback_v6) From b8ccb8c4d3c397e5aae62a595ba1af749587e8c7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 5 May 2026 04:17:02 -0400 Subject: [PATCH 2/2] Refactor socket and proxy. --- .github/workflows/ci.yml | 11 + Makefile.am | 4 +- .../libbitcoin-network.vcxproj | 4 +- .../libbitcoin-network.vcxproj.filters | 12 +- .../libbitcoin-network-test.vcxproj | 1 - .../libbitcoin-network-test.vcxproj.filters | 3 - .../libbitcoin-network.vcxproj | 4 +- .../libbitcoin-network.vcxproj.filters | 12 +- .../bitcoin/network/channels/channel_rpc.hpp | 3 +- include/bitcoin/network/error.hpp | 1 - .../network/impl/channels/channel_rpc.ipp | 68 ++--- .../network/impl/messages/json_body.ipp | 51 +++- .../bitcoin/network/messages/http_body.hpp | 52 ++-- .../bitcoin/network/messages/json_body.hpp | 2 +- include/bitcoin/network/messages/rpc/body.hpp | 4 +- include/bitcoin/network/net/proxy.hpp | 16 +- include/bitcoin/network/net/socket.hpp | 133 +++++---- src/channels/channel_http.cpp | 10 +- src/error.cpp | 1 - src/messages/rpc/body.cpp | 50 ++-- src/net/connector_socks.cpp | 20 +- src/net/proxy_actions.cpp | 41 +-- src/net/socket.cpp | 31 +- src/net/socket_body.cpp | 267 ++++++++++++++++++ src/net/socket_http.cpp | 4 +- src/net/socket_raw.cpp | 78 ----- src/net/socket_rpc.cpp | 204 ++----------- src/net/{socket_p2p.cpp => socket_tcp.cpp} | 16 +- src/net/socket_ws.cpp | 41 +++ test/error.cpp | 9 - test/messages/http_body_reader.cpp | 34 ++- test/messages/rpc/body_reader.cpp | 4 +- test/net/socket.cpp | 4 +- 33 files changed, 655 insertions(+), 540 deletions(-) create mode 100644 src/net/socket_body.cpp delete mode 100644 src/net/socket_raw.cpp rename src/net/{socket_p2p.cpp => socket_tcp.cpp} (78%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b37defc9b..a269ae9fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -578,6 +578,17 @@ jobs: platform: "x64" version: "vs2022" tests: "*" + - image: windows-2025-vs2026 + configuration: "StaticRelease" + platform: "x64" + version: "vs2026" + tests: "*" + + - image: windows-2025-vs2026 + configuration: "StaticDebug" + platform: "x64" + version: "vs2026" + tests: "*" runs-on: ${{ matrix.image }} diff --git a/Makefile.am b/Makefile.am index 1bbe3d5f1..c5beae820 100644 --- a/Makefile.am +++ b/Makefile.am @@ -108,12 +108,12 @@ src_libbitcoin_network_la_SOURCES = \ src/net/proxy_actions.cpp \ src/net/proxy_queue.cpp \ src/net/socket.cpp \ + src/net/socket_body.cpp \ src/net/socket_connect.cpp \ src/net/socket_http.cpp \ - src/net/socket_p2p.cpp \ - src/net/socket_raw.cpp \ src/net/socket_rpc.cpp \ src/net/socket_stop.cpp \ + src/net/socket_tcp.cpp \ src/net/socket_wait.cpp \ src/net/socket_ws.cpp \ src/protocols/protocol.cpp \ diff --git a/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj b/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj index f1eeba864..18f16a7ac 100644 --- a/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj @@ -202,12 +202,12 @@ + - - + diff --git a/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj.filters index b428ac415..e66de30ca 100644 --- a/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-network/libbitcoin-network.vcxproj.filters @@ -387,16 +387,13 @@ src\net - - src\net - - + src\net - + src\net - + src\net @@ -405,6 +402,9 @@ src\net + + src\net + src\net diff --git a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj index 592a59386..9b3ba599d 100644 --- a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj @@ -134,7 +134,6 @@ - $(IntDir)test_config_address.obj diff --git a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj.filters index 264dbeca4..1d5ff0a3c 100644 --- a/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-network-test/libbitcoin-network-test.vcxproj.filters @@ -153,9 +153,6 @@ src\channels - - src\channels - src\config diff --git a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj index 0b2b524c1..5bcdaee0e 100644 --- a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj +++ b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj @@ -128,7 +128,6 @@ - $(IntDir)src_config_address.obj @@ -203,6 +202,7 @@ + @@ -311,7 +311,6 @@ - @@ -426,7 +425,6 @@ - diff --git a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj.filters b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj.filters index 96ef47f7c..e66de30ca 100644 --- a/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj.filters +++ b/builds/msvc/vs2026/libbitcoin-network/libbitcoin-network.vcxproj.filters @@ -189,9 +189,6 @@ src\channels - - src\channels - src\config @@ -390,6 +387,9 @@ src\net + + src\net + src\net @@ -698,9 +698,6 @@ include\bitcoin\network\channels - - include\bitcoin\network\channels - include\bitcoin\network\channels @@ -1043,9 +1040,6 @@ include\bitcoin\network\protocols - - include\bitcoin\network\protocols - include\bitcoin\network\protocols diff --git a/include/bitcoin/network/channels/channel_rpc.hpp b/include/bitcoin/network/channels/channel_rpc.hpp index a7f55724e..3aaf697c9 100644 --- a/include/bitcoin/network/channels/channel_rpc.hpp +++ b/include/bitcoin/network/channels/channel_rpc.hpp @@ -91,8 +91,7 @@ class channel_rpc /// Handle send completion, invokes receive() for non-notifications. template inline void handle_send(const code& ec, size_t bytes, - const rpc::message_cptr& message, - const result_handler& handler) NOEXCEPT; + const std::string& method, const result_handler& handler) NOEXCEPT; /// Stranded handler invoked from stop(). inline void stopping(const code& ec) NOEXCEPT override; diff --git a/include/bitcoin/network/error.hpp b/include/bitcoin/network/error.hpp index 6c3d9c736..b8514061d 100644 --- a/include/bitcoin/network/error.hpp +++ b/include/bitcoin/network/error.hpp @@ -313,7 +313,6 @@ enum error_t : uint8_t jsonrpc_v1_requires_array_params, jsonrpc_v1_requires_id, jsonrpc_params_not_collection, - jsonrpc_reader_bad_buffer, jsonrpc_reader_stall, jsonrpc_reader_exception, jsonrpc_writer_exception diff --git a/include/bitcoin/network/impl/channels/channel_rpc.ipp b/include/bitcoin/network/impl/channels/channel_rpc.ipp index e5fa484f0..b523f0f65 100644 --- a/include/bitcoin/network/impl/channels/channel_rpc.ipp +++ b/include/bitcoin/network/impl/channels/channel_rpc.ipp @@ -55,8 +55,8 @@ inline void CLASS::resume() NOEXCEPT // Read cycle. // ---------------------------------------------------------------------------- +// Failure to call receive() after successful message handling stalls channel. -// Failure to call after successful message handling causes stalled channel. TEMPLATE inline void CLASS::receive() NOEXCEPT { @@ -69,19 +69,8 @@ inline void CLASS::receive() NOEXCEPT return; reading_ = true; - const auto in = emplace_shared - ( - // default json model, unused size_hint, unused serialization buffer. - json::json_value{}, + const auto in = to_shared(); - // default incoming rpc message. - rpc::request_t{}, - - // !strict allows params singleton to be accepted as array (Electrum). - false - ); - - // Post handle_read to strand upon stop, error, or buffer full. read(request_buffer(), *in, std::bind(&channel_rpc::handle_receive, shared_from_base(), _1, _2, in)); @@ -149,6 +138,15 @@ inline http::flat_buffer& CLASS::request_buffer() NOEXCEPT // Send. // ---------------------------------------------------------------------------- +template +std::string extract_method(const Message& message) NOEXCEPT +{ + if constexpr (is_same_type) + return "response: " + message.error.value_or(rpc::result_t{}).message; + else + return "request: " + message.method; +} + // protected TEMPLATE template @@ -157,53 +155,39 @@ inline void CLASS::send(Message&& message, size_t size_hint, { BC_ASSERT(stranded()); using namespace std::placeholders; - using namespace system; - - // Templated message due to notification sending request_t. - const auto out = emplace_shared> - ( - // default json model, buffer size_hint, default serialization buffer. - json::json_value{ {}, size_hint, {} }, - // outgoing rpc message (request_t or response_t). - std::forward(message), + auto complete = std::bind(&CLASS::handle_send, + shared_from_base(), _1, _2, extract_method(message), + std::move(handler)); - // unused strict json-rpc. - true - ); - - // Write message to socket, capture its pointer for lifetime. - write(*out, std::bind(&CLASS::handle_send, - shared_from_base(), _1, _2, out, std::move(handler))); + // Write message (response or notification) to socket. + write( + { + json::json_value{ .size_hint = size_hint }, + std::forward(message) + }, std::move(complete)); } // protected TEMPLATE template inline void CLASS::handle_send(const code& ec, size_t bytes, - const rpc::message_cptr& message, - const result_handler& handler) NOEXCEPT + const std::string& method, const result_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); - if (ec) stop(ec); + if (ec) + stop(ec); + + LOGA("Rpc " << method << " (" << bytes << ") bytes [" << endpoint() << "] "); // Typically a noop, but handshake may pause channel here. handler(ec); - // Only invoke continuation for a request response (not notification). + // Restart the listener (following response to request only). if constexpr (is_same_type) { - LOGA("Rpc response: (" << bytes << ") bytes [" << endpoint() << "] " - << message->message.error.value_or(rpc::result_t{}).message); - - // Continue the read loop (does not unpause or restart). receive(); } - else - { - LOGA("Rpc notification: (" << bytes << ") bytes [" << endpoint() << "] " - << message->message.method); - } } TEMPLATE diff --git a/include/bitcoin/network/impl/messages/json_body.ipp b/include/bitcoin/network/impl/messages/json_body.ipp index 8b5ef4d2b..e33b0bfe9 100644 --- a/include/bitcoin/network/impl/messages/json_body.ipp +++ b/include/bitcoin/network/impl/messages/json_body.ipp @@ -61,6 +61,19 @@ size_t CLASS::reader::put(const buffer_type& buffer, boost_code& ec) NOEXCEPT using namespace system; using namespace network::error; + const auto size = buffer.size(); + if (is_zero(size)) + { + ec.clear(); + return {}; + } + + if (is_null(buffer.data())) + { + ec = to_http_code(http_error_t::bad_alloc); + return {}; + } + try { const auto data = pointer_cast(buffer.data()); @@ -89,21 +102,32 @@ size_t CLASS::reader::put(const buffer_type& buffer, boost_code& ec) NOEXCEPT TEMPLATE void CLASS::reader::finish(boost_code& ec) NOEXCEPT { - ec.clear(); + // Finishing can be very confusing. The derived rpc body calls this base + // method, and then the virtual done() is tested to determine whether the + // logical object is fully read (including optionally required terminator). + // Any error code here signals the beast reader (and any reader that + // terminates based on the end of the framed data, such as websockets) that + // the parse has failed (terminal error). However for custom stream readers + // that may not be aware of byte termination, the `need_more` implies that + // the parse has not failed and that more bytes may be parsed. In either + // case, when this is called and the parse is complete, then the parsed + // json object is moved to the model and the parser is released. In the + // case of the derived json-rpc reader, the json is also then converted to + // the rpc model if valid, otherwise returning a failure code. In no case + // is the underlying parser_.finish(ec) ever called, as that would preclude + // use in the unbounded scenario. - // Calling parser.finish() when parser.done() results in error::incomplete. - if (!parser_.done()) + using namespace network::error; + if (!done()) { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - parser_.finish(ec); - BC_POP_WARNING() - - if (ec) return; + ec = to_http_code(http_error_t::need_more); + return; } try { - value_.model = parser_.release(); + ec.clear(); + value_.model = parser_.release(); } catch (const boost::system::system_error& e) { @@ -113,7 +137,6 @@ void CLASS::reader::finish(boost_code& ec) NOEXCEPT catch (...) { // As a catch-all we blame alloc. - using namespace network::error; ec = to_http_code(http_error_t::bad_alloc); } @@ -158,12 +181,18 @@ CLASS::writer::out_buffer CLASS::writer::get(boost_code& ec) NOEXCEPT { using namespace network::error; - if (serializer_.done()) + if (done()) { ec = to_http_code(http_error_t::end_of_stream); return {}; } + if (!value_.buffer) + { + ec = to_http_code(http_error_t::bad_alloc); + return {}; + } + const auto size = value_.buffer->max_size(); if (is_zero(size)) { diff --git a/include/bitcoin/network/messages/http_body.hpp b/include/bitcoin/network/messages/http_body.hpp index a4833d963..507f379b5 100644 --- a/include/bitcoin/network/messages/http_body.hpp +++ b/include/bitcoin/network/messages/http_body.hpp @@ -70,7 +70,8 @@ using body_writer = std::variant buffer_writer, // 16 bytes string_writer, // 8 bytes json_writer, // 136 bytes! - rpc::writer // 144 bytes! + rpc::writer, // 144 bytes! + rpc::notifier // 144 bytes! >; using empty_value = http::empty_body::value_type; @@ -177,25 +178,29 @@ struct BCT_API body /// Select reader based on content-type header. inline void assign_reader(header& header, value_type& value) NOEXCEPT { - switch (http::content_media_type(header)) + // Allows for preselection of reader for non-http reads. + if (!value_.has_value()) { - case http::media_type::application_json: - if (value_.plain_json) - value = json_value{}; - else - value = rpc::request{}; - break; - case http::media_type::text_plain: - value = string_value{}; - break; - case http::media_type::application_octet_stream: - if (http::has_attachment(header)) - value = file_value{}; - else - value = data_value{}; - break; - default: - value = empty_value{}; + switch (http::content_media_type(header)) + { + case http::media_type::application_json: + if (value_.plain_json) + value = json_value{}; + else + value = rpc::request{}; + break; + case http::media_type::text_plain: + value = string_value{}; + break; + case http::media_type::application_octet_stream: + if (http::has_attachment(header)) + value = file_value{}; + else + value = data_value{}; + break; + default: + value = empty_value{}; + } } std::visit(overload @@ -302,14 +307,17 @@ struct BCT_API body }, [&](rpc::response& value) NOEXCEPT { - // json_writer is not movable (by contained serializer). + // rpc::writer is not movable (by contained serializer). // So requires in-place construction for variant populate. return body_writer{ std::in_place_type, header, value }; }, - [&](rpc::request&) NOEXCEPT + [&](rpc::request& value) NOEXCEPT { - return body_writer{ std::monostate{} }; + // rpc::writer is not movable (by contained serializer). + // So requires in-place construction for variant populate. + return body_writer{ std::in_place_type, + header, value }; } }, value.value()); } diff --git a/include/bitcoin/network/messages/json_body.hpp b/include/bitcoin/network/messages/json_body.hpp index 89453e29d..0cc81acad 100644 --- a/include/bitcoin/network/messages/json_body.hpp +++ b/include/bitcoin/network/messages/json_body.hpp @@ -76,11 +76,11 @@ struct body protected: value_type& value_; - boost::json::stream_parser parser_{}; private: size_t total_{}; http::length_type expected_{}; + boost::json::stream_parser parser_{}; }; class writer diff --git a/include/bitcoin/network/messages/rpc/body.hpp b/include/bitcoin/network/messages/rpc/body.hpp index 09391ac4a..3f7eb8c96 100644 --- a/include/bitcoin/network/messages/rpc/body.hpp +++ b/include/bitcoin/network/messages/rpc/body.hpp @@ -33,7 +33,7 @@ struct message_type : public json::json_value { Type message{}; - bool strict{ true }; + bool strict{}; }; /// Derived boost::beast::http body for JSON-RPC messages. @@ -115,6 +115,8 @@ using response_cptr = std::shared_ptr; using response_ptr = std::shared_ptr; using writer = response_body::writer; +using notifier = rpc::request_body::writer; + // Allows request to be sent (for notifications). template using message_value = typename body::value_type; diff --git a/include/bitcoin/network/net/proxy.hpp b/include/bitcoin/network/net/proxy.hpp index 0fbd56db5..fd18eb1c1 100644 --- a/include/bitcoin/network/net/proxy.hpp +++ b/include/bitcoin/network/net/proxy.hpp @@ -122,7 +122,7 @@ class BCT_API proxy /// Cancel wait or any asynchronous read/write operation, handlers posted. virtual void cancel(result_handler&& handler) NOEXCEPT; - /// RAW (generic, variable size). + /// WS (generic, framed). /// ----------------------------------------------------------------------- /// Read complete logical message for websockets (not for tcp). @@ -135,7 +135,7 @@ class BCT_API proxy virtual void write(const asio::const_buffer& in, bool binary, count_handler&& handler) NOEXCEPT; - /// P2P (generic, fixed size, WS: always binary). + /// TCP (generic, fixed size). /// ----------------------------------------------------------------------- /// Read fixed-size TCP message from the remote endpoint into buffer. @@ -154,11 +154,11 @@ class BCT_API proxy count_handler&& handler) NOEXCEPT; /// Write rpc response to the socket (json buffer in body). - virtual void write(rpc::response& response, + virtual void write(rpc::response&& response, count_handler&& handler) NOEXCEPT; /// Write rpc notification (request) to the socket (json buffer in body). - virtual void write(rpc::request& notification, + virtual void write(rpc::request&& notification, count_handler&& handler) NOEXCEPT; /// HTTP (generic/rpc). @@ -177,13 +177,13 @@ class BCT_API proxy typedef std::deque queue; // For write buffering. - void do_raw_write(const asio::const_buffer& payload, bool binary, + void do_ws_write(const asio::const_buffer& payload, bool binary, const count_handler& handler) NOEXCEPT; - void do_p2p_write(const asio::const_buffer& payload, + void do_tcp_write(const asio::const_buffer& payload, const count_handler& handler) NOEXCEPT; - void do_response_write(const ref& response, + void do_response_write(const rpc::response_ptr& response, const count_handler& handler) NOEXCEPT; - void do_notification_write(const ref& notification, + void do_notification_write(const rpc::request_ptr& notification, const count_handler& handler) NOEXCEPT; void do_subscribe_stop(const result_handler& handler, const result_handler& complete) NOEXCEPT; diff --git a/include/bitcoin/network/net/socket.hpp b/include/bitcoin/network/net/socket.hpp index 72c3f854e..e69bfa077 100644 --- a/include/bitcoin/network/net/socket.hpp +++ b/include/bitcoin/network/net/socket.hpp @@ -110,28 +110,26 @@ class BCT_API socket virtual void connect(const asio::endpoints& range, result_handler&& handler) NOEXCEPT; - /// RAW (generic, variable size). + /// WS (generic, framed). /// ----------------------------------------------------------------------- - /// Read complete logical message for websockets (not for tcp). - /// Read available buffer from the socket, handler posted to socket strand. - virtual void raw_read(http::flat_buffer& out, + /// Read framed message from websocket, handler posted to socket strand. + virtual void ws_read(http::flat_buffer& out, count_handler&& handler) NOEXCEPT; - /// Binary or text mode applies to websockets (no-op for tcp). - /// Write the provided buffer to socket, handler posted to socket strand. - virtual void raw_write(const asio::const_buffer& in, bool binary, + /// Write buffer as frame to websocket, handler posted to socket strand. + virtual void ws_write(const asio::const_buffer& in, bool binary, count_handler&& handler) NOEXCEPT; - /// P2P (generic, fixed size). + /// TCP (generic, fixed size). /// ----------------------------------------------------------------------- - /// Read fixed buffer from the socket, handler posted to socket strand. - virtual void p2p_read(const asio::mutable_buffer& out, + /// Read fixed buffer from tcp socket, handler posted to socket strand. + virtual void tcp_read(const asio::mutable_buffer& out, count_handler&& handler) NOEXCEPT; - /// Write the provided buffer to socket, handler posted to socket strand. - virtual void p2p_write(const asio::const_buffer& in, + /// Write fixed buffer to socket, handler posted to socket strand. + virtual void tcp_write(const asio::const_buffer& in, count_handler&& handler) NOEXCEPT; /// RPC (TCP: electrum/stratum_v1, WS: btcd). @@ -142,11 +140,26 @@ class BCT_API socket count_handler&& handler) NOEXCEPT; /// Write rpc response to the socket, handler posted to socket strand. - virtual void rpc_write(rpc::response& response, + virtual void rpc_write(rpc::response&& response, count_handler&& handler) NOEXCEPT; /// Write rpc notification to the socket, handler posted to socket strand. - virtual void rpc_notify(rpc::request& notification, + virtual void rpc_notify(rpc::request&& notification, + count_handler&& handler) NOEXCEPT; + + /// BODY (non-http body read/write). + /// ----------------------------------------------------------------------- + + /// Read body request from the socket, handler posted to socket strand. + virtual void body_read(http::flat_buffer& buffer, http::request& request, + count_handler&& handler) NOEXCEPT; + + /// Write body response to the socket, handler posted to socket strand. + virtual void body_write(http::response&& response, + count_handler&& handler) NOEXCEPT; + + /// Write body notification to the socket, handler posted to socket strand. + virtual void body_notify(http::request&& notification, count_handler&& handler) NOEXCEPT; /// HTTP (generic/rpc). @@ -224,8 +237,8 @@ class BCT_API socket /// Variant (ws vs. tcp) helpers (protected by strand). /// ----------------------------------------------------------------------- - void async_read(http::flat_buffer& buffer, const count_handler& handler, - size_t size=rpc::writer::default_buffer) NOEXCEPT; + void async_read(http::flat_buffer& buffer, + const count_handler& handler) NOEXCEPT; void async_read(const asio::mutable_buffer& buffer, const count_handler& handler) NOEXCEPT; void async_read_some(const asio::mutable_buffer& buffer, @@ -237,46 +250,49 @@ class BCT_API socket using http_parser = boost::beast::http::request_parser; using http_parser_ptr = std::shared_ptr; - struct read_rpc + struct read_state { - typedef std::shared_ptr ptr; + typedef std::shared_ptr ptr; - read_rpc(rpc::request& request, http::flat_buffer& buffer) NOEXCEPT - : value{ request }, reader{ value }, buffer{ buffer } + read_state(http::request& request, http::flat_buffer& buffer) NOEXCEPT + : value{ request }, reader{ value.base(), value.body() }, + buffer{ buffer } { } - rpc::request& value; - rpc::request_body::reader reader; + http::request& value; + http::body::reader reader; http::flat_buffer& buffer; }; - struct write_rpc + struct write_state { - typedef std::shared_ptr ptr; - using out_buffer = rpc::writer::out_buffer; + typedef std::shared_ptr ptr; + using out_buffer = http::body::writer::out_buffer; - write_rpc(rpc::response& response) NOEXCEPT - : value{ response }, writer{ value } + write_state(http::response&& response) NOEXCEPT + : value{ std::move(response) }, writer{ value.base(), value.body() } { } - rpc::response& value; - rpc::response_body::writer writer; + bool more{ true }; + http::response value; + http::body::writer writer; }; - struct notify_rpc + struct notify_state { - typedef std::shared_ptr ptr; - using out_buffer = rpc::writer::out_buffer; + typedef std::shared_ptr ptr; + using out_buffer = http::body::writer::out_buffer; - notify_rpc(rpc::request& request) NOEXCEPT - : value{ request }, writer{ value } + notify_state(http::request&& request) NOEXCEPT + : value{ std::move(request) }, writer{ value.base(), value.body() } { } - rpc::request& value; - rpc::request_body::writer writer; + bool more{ true }; + http::request value; + http::body::writer writer; }; // do @@ -299,23 +315,25 @@ class BCT_API socket const result_handler& handler) NOEXCEPT; void do_handshake(const result_handler& handler) NOEXCEPT; - // raw (tcp/ws) - void do_raw_read(ref out, + // ws (framed) + void do_ws_read(ref out, const count_handler& handler) NOEXCEPT; - void do_raw_write(const asio::const_buffer& in, bool binary, + void do_ws_write(const asio::const_buffer& in, bool binary, const count_handler& handler) NOEXCEPT; - // p2p (tcp/ws) - void do_p2p_read(const asio::mutable_buffer& out, + // tcp (fixed) + void do_tcp_read(const asio::mutable_buffer& out, const count_handler& handler) NOEXCEPT; - // rpc (tcp/ws) - void do_rpc_read(boost_code ec, size_t total, - const read_rpc::ptr& in, const count_handler& handler) NOEXCEPT; - void do_rpc_write(boost_code ec, size_t total, - const write_rpc::ptr& out, const count_handler& handler) NOEXCEPT; - void do_rpc_notify(boost_code ec, size_t total, - const notify_rpc::ptr& out, const count_handler& handler) NOEXCEPT; + // body + void do_body_read(boost_code ec, size_t total, + const read_state::ptr& in, const count_handler& handler) NOEXCEPT; + void handle_body_read(boost_code ec, size_t size, size_t total, + const read_state::ptr& in, const count_handler& handler) NOEXCEPT; + void do_body_write(boost_code ec, size_t total, + const write_state::ptr& out, const count_handler& handler) NOEXCEPT; + void do_body_notify(boost_code ec, size_t total, + const notify_state::ptr& out, const count_handler& handler) NOEXCEPT; // http void do_http_read(ref buffer, @@ -347,20 +365,23 @@ class BCT_API socket void handle_handshake(const boost_code& ec, const result_handler& handler) NOEXCEPT; - // raw/p2p/read/write (tcp/ws) + // read/write (tcp/ws) void handle_async(const boost_code& ec, size_t size, const count_handler& handler,const std::string& operation) NOEXCEPT; void handle_async_read(const boost_code& ec, size_t size, const asio::mutable_buffer& buffer, const http::flat_buffer_ptr& flat, const count_handler& handler) NOEXCEPT; - // rpc (tcp/ws) - void handle_rpc_read(boost_code ec, size_t size, size_t total, - const read_rpc::ptr& in, const count_handler& handler) NOEXCEPT; - void handle_rpc_write(boost_code ec, size_t size, size_t total, - const write_rpc::ptr& out, const count_handler& handler) NOEXCEPT; - void handle_rpc_notify(boost_code ec, size_t size, size_t total, - const notify_rpc::ptr& out, const count_handler& handler) NOEXCEPT; + // rpc + void handle_rpc_read(const code& ec, size_t bytes, + const ref& out, const http::request_ptr& in, + const count_handler& handler) NOEXCEPT; + + // body + void handle_body_write(boost_code ec, size_t size, size_t total, + const write_state::ptr& out, const count_handler& handler) NOEXCEPT; + void handle_body_notify(boost_code ec, size_t size, size_t total, + const notify_state::ptr& out, const count_handler& handler) NOEXCEPT; // http (generic/rpc) void handle_http_read(const boost_code& ec, size_t size, diff --git a/src/channels/channel_http.cpp b/src/channels/channel_http.cpp index 5d9d6427b..bf03585e5 100644 --- a/src/channels/channel_http.cpp +++ b/src/channels/channel_http.cpp @@ -165,18 +165,18 @@ void channel_http::send(response&& response, result_handler&& handler) NOEXCEPT BC_ASSERT(stranded()); assign_json_buffer(response); - const auto ptr = system::move_shared(std::move(response)); + const auto out = system::move_shared(std::move(response)); count_handler complete = std::bind(&channel_http::handle_send, - shared_from_base(), _1, _2, ptr, std::move(handler)); + shared_from_base(), _1, _2, out, std::move(handler)); - if (!ptr) + if (!out) { complete(error::bad_alloc, {}); return; } - // response has been moved to ptr. - write(*ptr, std::move(complete)); + // response has been moved to out. + write(*out, std::move(complete)); } void channel_http::handle_send(const code& ec, size_t bytes, diff --git a/src/error.cpp b/src/error.cpp index 4493b0ea6..ea857ec64 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -300,7 +300,6 @@ DEFINE_ERROR_T_MESSAGE_MAP(error) { jsonrpc_v1_requires_array_params, "jsonrpc v1 requires array params" }, { jsonrpc_v1_requires_id, "jsonrpc v1 requires id" }, { jsonrpc_params_not_collection, "jsonrpc params not collection" }, - { jsonrpc_reader_bad_buffer, "jsonrpc reader bad buffer" }, { jsonrpc_reader_stall, "jsonrpc reader stall" }, { jsonrpc_reader_exception, "jsonrpc reader exception" }, { jsonrpc_writer_exception, "jsonrpc writer exception" } diff --git a/src/messages/rpc/body.cpp b/src/messages/rpc/body.cpp index a98ea7022..26b2ac993 100644 --- a/src/messages/rpc/body.cpp +++ b/src/messages/rpc/body.cpp @@ -35,6 +35,18 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) BC_PUSH_WARNING(NO_UNGUARDED_POINTERS) BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) +constexpr bool is_rpc_terminator(char character) NOEXCEPT +{ + // Batched rpc messages are array-terminated. + return character == '}' || character == ']'; +} + +constexpr bool is_electrum_terminator(char character) NOEXCEPT +{ + // Electrum rpc messages require a trailing newline. + return character == '\n'; +} + // rpc::body::reader // ---------------------------------------------------------------------------- @@ -42,21 +54,11 @@ template <> size_t body::reader:: put(const buffer_type& buffer, boost_code& ec) NOEXCEPT { - const auto size = buffer.size(); - if (is_zero(size)) - { - ec.clear(); - return {}; - } - - if (is_null(buffer.data())) - { - ec = code{ error::jsonrpc_reader_bad_buffer }; - return {}; - } - + // Null and empty guarded in base reader. const auto parsed = base::reader::put(buffer, ec); - if (ec || !parser_.done()) + + // Not done until terminated and can't search for it until parser is done. + if (ec || !base::reader::done()) return parsed; // http json does not use termination. @@ -71,20 +73,23 @@ put(const buffer_type& buffer, boost_code& ec) NOEXCEPT return parsed; } - // boost::json consumes whitespace, and leaves any subsequent chars - // unparsed, so terminator must be in the parsed segment of the buffer. const auto data = pointer_cast(buffer.data()); - for (auto index = parsed; !is_zero(index);) + + // boost::json consumes whitespace, and leaves any subsequent chars + // unparsed, so terminator must be in the parsed buffer segment. + for (auto index = parsed; !is_zero(index); --index) { - if (data[--index] == '\n') + const auto character = data[sub1(index)]; + if (is_electrum_terminator(character)) { - // There may be unparsed characters (ok, next message). has_terminator_ = true; - return parsed; + break; } + + if (is_rpc_terminator(character)) + break; } - // There is no terminator (yet). return parsed; } @@ -93,13 +98,14 @@ bool body::reader:: done() const NOEXCEPT { // Parser may be done but with terminator still outstanding. - return parser_.done() && (!terminated_ || has_terminator_); + return base::reader::done() && (!terminated_ || has_terminator_); } template <> void body::reader:: finish(boost_code& ec) NOEXCEPT { + // See notes in base reader. base::reader::finish(ec); if (ec) return; diff --git a/src/net/connector_socks.cpp b/src/net/connector_socks.cpp index f41e43e36..1e07479d8 100644 --- a/src/net/connector_socks.cpp +++ b/src/net/connector_socks.cpp @@ -177,7 +177,7 @@ void connector_socks::do_socks_greeting_write(const code& ec, // Start of socket strand sequence. // All socket operations are dispatched to its own strand, so this write // will be posted before invocation. This makes socket calls thread safe. - socket->p2p_write({ greeting->data(), greeting->size() }, + socket->tcp_write({ greeting->data(), greeting->size() }, std::bind(&connector_socks::handle_socks_greeting_write, shared_from_base(), _1, _2, socket, greeting)); @@ -202,7 +202,7 @@ void connector_socks::handle_socks_greeting_write(const code& ec, size_t size, const auto response = emplace_shared>(); - socket->p2p_read({ response->data(), response->size() }, + socket->tcp_read({ response->data(), response->size() }, std::bind(&connector_socks::handle_socks_method_read, shared_from_base(), _1, _2, socket, response)); @@ -273,7 +273,7 @@ void connector_socks::handle_socks_method_read(const code& ec, size_t size, *it++ = narrow_cast(password_length); it = std::copy(password.begin(), password.end(), it); - socket->p2p_write({ authenticator->data(), authenticator->size() }, + socket->tcp_write({ authenticator->data(), authenticator->size() }, std::bind(&connector_socks::handle_socks_authentication_write, shared_from_base(), _1, _2, socket, authenticator)); @@ -299,7 +299,7 @@ void connector_socks::handle_socks_authentication_write(const code& ec, const auto auth_res = emplace_shared>(); - socket->p2p_read({ auth_res->data(), auth_res->size() }, + socket->tcp_read({ auth_res->data(), auth_res->size() }, std::bind(&connector_socks::handle_socks_authentication_read, shared_from_base(), _1, _2, socket, auth_res)); @@ -404,7 +404,7 @@ void connector_socks::do_socks_connect_write( it = std::copy(port.begin(), port.end(), it); } - socket->p2p_write({ request->data(), request->size() }, + socket->tcp_write({ request->data(), request->size() }, std::bind(&connector_socks::handle_socks_connect_write, shared_from_base(), _1, _2, socket, request)); @@ -429,7 +429,7 @@ void connector_socks::handle_socks_connect_write(const code& ec, size_t size, const auto response = emplace_shared>(); - socket->p2p_read({ response->data(), response->size() }, + socket->tcp_read({ response->data(), response->size() }, std::bind(&connector_socks::handle_socks_response_read, shared_from_base(), _1, _2, socket, response)); @@ -473,7 +473,7 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, // A version-4 IP address with length of 4 octets. const auto address = emplace_shared(4u + port_size); - socket->p2p_read({ address->data(), address->size() }, + socket->tcp_read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), _1, _2, socket, address)); @@ -484,7 +484,7 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, // A version-6 IP address with length of 16 octets. const auto address = emplace_shared(16u + port_size); - socket->p2p_read({ address->data(), address->size() }, + socket->tcp_read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), _1, _2, socket, address)); @@ -497,7 +497,7 @@ void connector_socks::handle_socks_response_read(const code& ec, size_t size, // of name that follow (and excludes two byte length of the port). const auto length = emplace_shared>(); - socket->p2p_read({ length->data(), sizeof(uint8_t) }, + socket->tcp_read({ length->data(), sizeof(uint8_t) }, std::bind(&connector_socks::handle_socks_length_read, shared_from_base(), _1, _2, socket, length)); @@ -532,7 +532,7 @@ void connector_socks::handle_socks_length_read(const code& ec, size_t size, const auto length = host_length->front() + port_size; const auto address = emplace_shared(length); - socket->p2p_read({ address->data(), address->size() }, + socket->tcp_read({ address->data(), address->size() }, std::bind(&connector_socks::handle_socks_address_read, shared_from_base(), _1, _2, socket, address)); diff --git a/src/net/proxy_actions.cpp b/src/net/proxy_actions.cpp index bd9403888..5e6022bd0 100644 --- a/src/net/proxy_actions.cpp +++ b/src/net/proxy_actions.cpp @@ -28,6 +28,7 @@ namespace network { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) +using namespace system; using namespace std::placeholders; // Wait (all). @@ -43,19 +44,19 @@ void proxy::cancel(result_handler&& handler) NOEXCEPT socket_->cancel(std::move(handler)); } -// RAW (generic, variable size). +// WS (generic, framed). // ---------------------------------------------------------------------------- void proxy::read(http::flat_buffer& out, count_handler&& handler) NOEXCEPT { do_reading(); - socket_->raw_read(out, std::move(handler)); + socket_->ws_read(out, std::move(handler)); } void proxy::write(const asio::const_buffer& in, bool binary, count_handler&& handler) NOEXCEPT { - writer call = std::bind(&proxy::do_raw_write, + writer call = std::bind(&proxy::do_ws_write, shared_from_this(), in, binary, std::move(handler)); boost::asio::dispatch(strand(), @@ -64,28 +65,28 @@ void proxy::write(const asio::const_buffer& in, bool binary, } // private -void proxy::do_raw_write(const asio::const_buffer& payload, bool binary, +void proxy::do_ws_write(const asio::const_buffer& payload, bool binary, const count_handler& handler) NOEXCEPT { - socket_->raw_write({ payload.data(), payload.size() }, binary, + socket_->ws_write({ payload.data(), payload.size() }, binary, std::bind(&proxy::handle_write, shared_from_this(), _1, _2, handler)); } -// P2P (generic, fixed size). +// TCP (generic, fixed size). // ---------------------------------------------------------------------------- void proxy::read(const asio::mutable_buffer& out, count_handler&& handler) NOEXCEPT { do_reading(); - socket_->p2p_read(out, std::move(handler)); + socket_->tcp_read(out, std::move(handler)); } void proxy::write(const asio::const_buffer& in, count_handler&& handler) NOEXCEPT { - writer call = std::bind(&proxy::do_p2p_write, + writer call = std::bind(&proxy::do_tcp_write, shared_from_this(), in, std::move(handler)); boost::asio::dispatch(strand(), @@ -94,10 +95,10 @@ void proxy::write(const asio::const_buffer& in, } // private -void proxy::do_p2p_write(const asio::const_buffer& payload, +void proxy::do_tcp_write(const asio::const_buffer& payload, const count_handler& handler) NOEXCEPT { - socket_->p2p_write({ payload.data(), payload.size() }, + socket_->tcp_write({ payload.data(), payload.size() }, std::bind(&proxy::handle_write, shared_from_this(), _1, _2, handler)); } @@ -112,20 +113,24 @@ void proxy::read(http::flat_buffer& buffer, rpc::request& request, socket_->rpc_read(buffer, request, std::move(handler)); } -void proxy::write(rpc::response& response, count_handler&& handler) NOEXCEPT +void proxy::write(rpc::response&& response, count_handler&& handler) NOEXCEPT { + // Pointer ships moveable message through the send queue. + const auto out = move_shared(std::move(response)); writer call = std::bind(&proxy::do_response_write, - shared_from_this(), std::ref(response), std::move(handler)); + shared_from_this(), out, std::move(handler)); boost::asio::dispatch(strand(), std::bind(&proxy::do_write, shared_from_this(), std::move(call))); } -void proxy::write(rpc::request& notification, count_handler&& handler) NOEXCEPT +void proxy::write(rpc::request&& notification, count_handler&& handler) NOEXCEPT { + // Pointer ships moveable message through the send queue. + const auto out = move_shared(std::move(notification)); writer call = std::bind(&proxy::do_notification_write, - shared_from_this(), std::ref(notification), std::move(handler)); + shared_from_this(), out, std::move(handler)); boost::asio::dispatch(strand(), std::bind(&proxy::do_write, @@ -133,19 +138,19 @@ void proxy::write(rpc::request& notification, count_handler&& handler) NOEXCEPT } // private -void proxy::do_response_write(const ref& response, +void proxy::do_response_write(const rpc::response_ptr& response, const count_handler& handler) NOEXCEPT { - socket_->rpc_write(response.get(), + socket_->rpc_write(std::move(*response), std::bind(&proxy::handle_write, shared_from_this(), _1, _2, handler)); } // private -void proxy::do_notification_write(const ref& notification, +void proxy::do_notification_write(const rpc::request_ptr& notification, const count_handler& handler) NOEXCEPT { - socket_->rpc_notify(notification.get(), + socket_->rpc_notify(std::move(*notification), std::bind(&proxy::handle_write, shared_from_this(), _1, _2, handler)); } diff --git a/src/net/socket.cpp b/src/net/socket.cpp index 27213617a..1c0da390f 100644 --- a/src/net/socket.cpp +++ b/src/net/socket.cpp @@ -265,6 +265,7 @@ asio::ssl::socket& socket::get_ssl() NOEXCEPT // ---------------------------------------------------------------------------- // protected +// write framed (ws) or unframed (tcp) fixed size (const buffer size). void socket::async_write(const asio::const_buffer& buffer, bool binary, const count_handler& handler) NOEXCEPT { @@ -293,7 +294,7 @@ void socket::async_write(const asio::const_buffer& buffer, bool binary, } } - +// read some (up to mutable buffer capacity). void socket::async_read_some(const asio::mutable_buffer& buffer, const count_handler& handler) NOEXCEPT { @@ -319,35 +320,33 @@ void socket::async_read_some(const asio::mutable_buffer& buffer, } } -// raw +// read framed/ws (fails if beyond flat buffer capacity). void socket::async_read(http::flat_buffer& buffer, - const count_handler& handler, size_t size) NOEXCEPT + const count_handler& handler) NOEXCEPT { try { if (is_websocket()) { buffer.consume(buffer.size()); - - // Complete logical message semantics. VARIANT_DISPATCH_METHOD(get_ws(), async_read(buffer, std::bind(&socket::handle_async, - shared_from_this(), _1, _2, handler, "async_read_raw"))); + shared_from_this(), _1, _2, handler, "async_read"))); } else { - // Any available data semantics (ok). - async_read_some(buffer.prepare(size), handler); + // Use async_read_some() or async_read(mutable_buffer). + handler(error::operation_failed, {}); } } catch (const std::exception& e) { - LOGF("Exception @ async_read_raw: " << e.what()); + LOGF("Exception @ async_read: " << e.what()); handler(error::operation_failed, {}); } } -// fixed/p2p +// read fixed/p2p (waits until mutable buffer is filled). void socket::async_read(const asio::mutable_buffer& buffer, const count_handler& handler) NOEXCEPT { @@ -355,24 +354,20 @@ void socket::async_read(const asio::mutable_buffer& buffer, { if (is_websocket()) { - const auto flat = std::make_shared(); - count_handler complete = std::bind(&socket::handle_async_read, - shared_from_this(), _1, _2, buffer, flat, handler); - - // Websocket doesn't have fixed-size reads, so simulate it. - async_read(*flat, std::move(complete)); + // Websockets read frame not size, use async_read(flat_buffer). + handler(error::operation_failed, {}); } else { // Fixed-size semantics. VARIANT_DISPATCH_FUNCTION(boost::asio::async_read, get_tcp(), buffer, std::bind(&socket::handle_async, - shared_from_this(), _1, _2, handler, "async_read_fixed")); + shared_from_this(), _1, _2, handler, "async_read")); } } catch (const std::exception& e) { - LOGF("Exception @ async_read_fixed: " << e.what()); + LOGF("Exception @ async_read: " << e.what()); handler(error::operation_failed, {}); } } diff --git a/src/net/socket_body.cpp b/src/net/socket_body.cpp new file mode 100644 index 000000000..46f50c903 --- /dev/null +++ b/src/net/socket_body.cpp @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include + +#include +#include +#include +#include + +namespace libbitcoin { +namespace network { + +using namespace system; +using namespace network::rpc; +using namespace std::placeholders; + +// Shared pointers required in handler parameters so closures control lifetime. +BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) +BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +// BODY (read). +// ---------------------------------------------------------------------------- +// The body_ methods accept any boost::beast-compliant body to read/write a tcp +// socket. For http read/write use the http_ methods. The rcp_ methods are just +// a specialization of these methods, passing the rpc::body<> types. For simple +// fixed-size tcp (p2p) use the tcp_ methods, and for simple framed ws use the +// ws_ methods. The body methods require fixed size or framed read/write. The +// json and json-rpc bodies are internally framed by json, so can read/write +// over a raw tcp socket (electrum) or a framed websocket (btcd). And again for +// http framing, use the http_ methods, as these incorporate header processing. + +void socket::body_read(http::flat_buffer& buffer, http::request& request, + count_handler&& handler) NOEXCEPT +{ + boost_code ec{}; + const auto in = emplace_shared(request, buffer); + in->reader.init({}, ec); + + boost::asio::dispatch(strand_, + std::bind(&socket::do_body_read, + shared_from_this(), ec, zero, in, std::move(handler))); +} + +// private +void socket::do_body_read(boost_code ec, size_t total, + const read_state::ptr& in, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + constexpr auto size = rpc::writer::default_buffer; + + if (ec) + { + const auto code = error::http_to_error_code(ec); + if (code == error::unknown) logx("body-read", ec); + handler(code, total); + return; + } + + async_read_some(in->buffer.prepare(size), + std::bind(&socket::handle_body_read, + shared_from_this(), _1, _2, total, in, handler)); +} + +// private +void socket::handle_body_read(boost_code ec, size_t size, size_t total, + const read_state::ptr& in, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + total = ceilinged_add(total, size); + if (error::asio_is_canceled(ec)) + { + handler(error::channel_stopped, total); + return; + } + + if (total > maximum_) + { + handler(error::message_overflow, total); + return; + } + + if (!ec) + { + in->buffer.commit(size); + in->buffer.consume(in->reader.put(in->buffer.data(), ec)); + if (!ec) + { + // The json/json-rpc readers do not finalize on finish, instead + // they return need_more if not complete and success if complete. + in->reader.finish(ec); + + if (!ec) + { + handler(error::success, total); + return; + } + + if (ec == error::http_error_t::need_more) + ec.clear(); + } + } + + // Handle error condition or incomplete message. + do_body_read(ec, total, in, handler); +} + +// Body (write). +// ---------------------------------------------------------------------------- + +void socket::body_write(http::response&& response, + count_handler&& handler) NOEXCEPT +{ + boost_code ec{}; + const auto out = emplace_shared(std::move(response)); + out->writer.init(ec); + + boost::asio::dispatch(strand_, + std::bind(&socket::do_body_write, + shared_from_this(), ec, zero, out, std::move(handler))); +} + +// private +void socket::do_body_write(boost_code ec, size_t total, + const write_state::ptr& out, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + const auto buffer = ec ? write_state::out_buffer{} : out->writer.get(ec); + if (!buffer.has_value()) + { + handler(error::bad_stream, total); + return; + } + + if (ec) + { + const auto code = error::http_to_error_code(ec); + if (code == error::unknown) logx("body-write", ec); + handler(code, total); + return; + } + + out->more = buffer.value().second; + const auto& data = buffer.value().first; + + // TODO: derive websocket binary/text from body type mapping. + async_write(data, false, + std::bind(&socket::handle_body_write, + shared_from_this(), _1, _2, total, out, handler)); +} + +// private +void socket::handle_body_write(boost_code ec, size_t size, size_t total, + const write_state::ptr& out, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + total = ceilinged_add(total, size); + if (error::asio_is_canceled(ec)) + { + handler(error::channel_stopped, total); + return; + } + + if (!ec && !out->more) + { + handler(error::success, total); + return; + } + + // Handle error condition or incomplete message. + do_body_write(ec, total, out, handler); +} + +// Body (notify). +// ---------------------------------------------------------------------------- +// Identical to body_write() apart from request vs. response. + +void socket::body_notify(http::request&& notification, + count_handler&& handler) NOEXCEPT +{ + boost_code ec{}; + const auto out = emplace_shared(std::move(notification)); + out->writer.init(ec); + + boost::asio::dispatch(strand_, + std::bind(&socket::do_body_notify, + shared_from_this(), ec, zero, out, std::move(handler))); +} + +// private +void socket::do_body_notify(boost_code ec, size_t total, + const notify_state::ptr& out, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + const auto buffer = ec ? notify_state::out_buffer{} : out->writer.get(ec); + if (!buffer.has_value()) + { + handler(error::bad_stream, total); + return; + } + + if (ec) + { + const auto code = error::http_to_error_code(ec); + if (code == error::unknown) logx("body-notify", ec); + handler(code, total); + return; + } + + out->more = buffer.value().second; + const auto& data = buffer.value().first; + + // TODO: derive websocket binary/text from body type mapping. + async_write(data, false, + std::bind(&socket::handle_body_notify, + shared_from_this(), _1, _2, total, out, handler)); +} + +// private +void socket::handle_body_notify(boost_code ec, size_t size, size_t total, + const notify_state::ptr& out, const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + + total = ceilinged_add(total, size); + if (error::asio_is_canceled(ec)) + { + handler(error::channel_stopped, total); + return; + } + + if (!ec && !out->more) + { + handler(error::success, total); + return; + } + + // Handle error condition or incomplete message. + do_body_notify(ec, total, out, handler); +} + +BC_POP_WARNING() +BC_POP_WARNING() +BC_POP_WARNING() + +} // namespace network +} // namespace libbitcoin diff --git a/src/net/socket_http.cpp b/src/net/socket_http.cpp index 022ef88a5..2cd9a3a18 100644 --- a/src/net/socket_http.cpp +++ b/src/net/socket_http.cpp @@ -111,7 +111,7 @@ void socket::handle_http_read(const boost_code& ec, size_t size, } const auto code = error::http_to_error_code(ec); - if (code == error::unknown) logx("http", ec); + if (code == error::unknown) logx("http-read", ec); handler(code, size); } @@ -165,7 +165,7 @@ void socket::handle_http_write(const boost_code& ec, size_t size, } const auto code = error::http_to_error_code(ec); - if (code == error::unknown) logx("http", ec); + if (code == error::unknown) logx("http-write", ec); handler(code, size); } diff --git a/src/net/socket_raw.cpp b/src/net/socket_raw.cpp deleted file mode 100644 index 59145e936..000000000 --- a/src/net/socket_raw.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) 2011-2026 libbitcoin developers (see AUTHORS) - * - * This file is part of libbitcoin. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -#include - -#include -#include -#include - -namespace libbitcoin { -namespace network { - -using namespace std::placeholders; - -BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - -// RAW (read). -// ---------------------------------------------------------------------------- - -// The flat_buffer must have sufficient capacity already allocated before the -// RAW read is initiated. If the incoming WebSocket message exceeds the current -// capacity, the read will fail or truncate. -void socket::raw_read(http::flat_buffer& out, - count_handler&& handler) NOEXCEPT -{ - boost::asio::dispatch(strand_, - std::bind(&socket::do_raw_read, - shared_from_this(), std::ref(out), std::move(handler))); -} - -// private -void socket::do_raw_read(ref out, - const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - async_read(out.get(), handler); -} - -// RAW (write). -// ---------------------------------------------------------------------------- - -// The const_buffer is fully allocated before write, so this is currently -// identical to p2p write. -void socket::raw_write(const asio::const_buffer& in, bool binary, - count_handler&& handler) NOEXCEPT -{ - boost::asio::dispatch(strand_, - std::bind(&socket::do_raw_write, - shared_from_this(), in, binary, std::move(handler))); -} - -// private -void socket::do_raw_write(const asio::const_buffer& in, bool binary, - const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - async_write(in, binary, handler); -} - -BC_POP_WARNING() - -} // namespace network -} // namespace libbitcoin diff --git a/src/net/socket_rpc.cpp b/src/net/socket_rpc.cpp index 564a92108..88582469c 100644 --- a/src/net/socket_rpc.cpp +++ b/src/net/socket_rpc.cpp @@ -18,16 +18,13 @@ */ #include -#include #include #include -#include namespace libbitcoin { namespace network { using namespace system; -using namespace network::rpc; using namespace std::placeholders; // Shared pointers required in handler parameters so closures control lifetime. @@ -35,207 +32,50 @@ BC_PUSH_WARNING(NO_VALUE_OR_CONST_REF_SHARED_PTR) BC_PUSH_WARNING(SMART_PTR_NOT_NEEDED) BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) -// RPC (read). -// ---------------------------------------------------------------------------- - void socket::rpc_read(http::flat_buffer& buffer, rpc::request& request, count_handler&& handler) NOEXCEPT { - boost_code ec{}; - const auto in = emplace_shared(request, buffer); - in->reader.init({}, ec); - - boost::asio::dispatch(strand_, - std::bind(&socket::do_rpc_read, - shared_from_this(), ec, zero, in, std::move(handler))); -} - -// private -void socket::do_rpc_read(boost_code ec, size_t total, - const read_rpc::ptr& in, const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - constexpr auto size = rpc::writer::default_buffer; + // Create variant http request to capture read. + const auto in = to_shared(); - if (ec) - { - // Json parser emits rpc, http and json codes. - const auto code = error::rpc_to_error_code(ec); - if (code == error::unknown) logx("rpc-read", ec); - handler(code, total); - return; - } + // Preselect rpc::request body value type. + in->body() = rpc::request{}; - async_read_some(in->buffer.prepare(size), + // Capture body and move it back into request reference. + body_read(buffer, *in, std::bind(&socket::handle_rpc_read, - shared_from_this(), _1, _2, total, in, handler)); + shared_from_this(), _1, _2, std::ref(request), in, + std::move(handler))); } // private -void socket::handle_rpc_read(boost_code ec, size_t size, size_t total, - const read_rpc::ptr& in, const count_handler& handler) NOEXCEPT +void socket::handle_rpc_read(const code& ec, size_t bytes, + const ref& out, const http::request_ptr& in, + const count_handler& handler) NOEXCEPT { - BC_ASSERT(stranded()); - - total = ceilinged_add(total, size); - if (error::asio_is_canceled(ec)) - { - handler(error::channel_stopped, total); - return; - } - - if (total > maximum_) - { - handler(error::message_overflow, total); - return; - } - if (!ec) { - in->buffer.commit(size); - const auto data = in->buffer.data(); - const auto parsed = in->reader.put(data, ec); - if (!ec) - { - in->buffer.consume(parsed); - if (in->reader.done()) - { - in->reader.finish(ec); - if (!ec) - { - handler(error::success, total); - return; - } - } - } + // Move rpc::request from http body value to caller out param. + out.get() = std::move(std::get(in->body().value())); } - // Handle error condition or incomplete message. - do_rpc_read(ec, total, in, handler); + handler(ec, bytes); } -// RPC (write). -// ---------------------------------------------------------------------------- - -void socket::rpc_write(rpc::response& response, +void socket::rpc_write(rpc::response&& response, count_handler&& handler) NOEXCEPT { - boost_code ec{}; - const auto out = emplace_shared(response); - out->writer.init(ec); - - // Dispatch success or fail, for handler invoke on strand. - boost::asio::dispatch(strand_, - std::bind(&socket::do_rpc_write, - shared_from_this(), ec, zero, out, std::move(handler))); + http::response out{}; + out.body() = std::move(response); + body_write(std::move(out), std::move(handler)); } -// private -void socket::do_rpc_write(boost_code ec, size_t total, - const write_rpc::ptr& out, const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - const auto buffer = ec ? write_rpc::out_buffer{} : out->writer.get(ec); - if (ec) - { - // Json serializer emits rpc, http and json codes. - const auto code = error::rpc_to_error_code(ec); - if (code == error::unknown) logx("rpc-write", ec); - handler(code, total); - return; - } - - BC_ASSERT(buffer.has_value()); - - async_write(buffer.value().first, false, - std::bind(&socket::handle_rpc_write, - shared_from_this(), _1, _2, total, out, handler)); -} - -// private -void socket::handle_rpc_write(boost_code ec, size_t size, size_t total, - const write_rpc::ptr& out, const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - total = ceilinged_add(total, size); - if (error::asio_is_canceled(ec)) - { - handler(error::channel_stopped, total); - return; - } - - if (!ec && out->writer.done()) - { - handler(error::success, total); - return; - } - - // Handle error condition or incomplete message. - do_rpc_write(ec, total, out, handler); -} - -/// RPC (notify). -// ---------------------------------------------------------------------------- - -void socket::rpc_notify(rpc::request& notification, +void socket::rpc_notify(rpc::request&& notification, count_handler&& handler) NOEXCEPT { - boost_code ec{}; - const auto out = emplace_shared(notification); - out->writer.init(ec); - - // Dispatch success or fail, for handler invoke on strand. - boost::asio::dispatch(strand_, - std::bind(&socket::do_rpc_notify, - shared_from_this(), ec, zero, out, std::move(handler))); -} - -// private -void socket::do_rpc_notify(boost_code ec, size_t total, - const notify_rpc::ptr& out, const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - const auto buffer = ec ? notify_rpc::out_buffer{} : out->writer.get(ec); - if (ec) - { - // Json serializer emits rpc, http and json codes. - const auto code = error::rpc_to_error_code(ec); - if (code == error::unknown) logx("rpc-notify", ec); - handler(code, total); - return; - } - - BC_ASSERT(buffer.has_value()); - - async_write(buffer.value().first, false, - std::bind(&socket::handle_rpc_notify, - shared_from_this(), _1, _2, total, out, handler)); -} - -// private -void socket::handle_rpc_notify(boost_code ec, size_t size, size_t total, - const notify_rpc::ptr& out, const count_handler& handler) NOEXCEPT -{ - BC_ASSERT(stranded()); - - total = ceilinged_add(total, size); - if (error::asio_is_canceled(ec)) - { - handler(error::channel_stopped, total); - return; - } - - if (!ec && out->writer.done()) - { - handler(error::success, total); - return; - } - - // Handle error condition or incomplete message. - do_rpc_notify(ec, total, out, handler); + http::request out{}; + out.body() = std::move(notification); + body_notify(std::move(out), std::move(handler)); } BC_POP_WARNING() diff --git a/src/net/socket_p2p.cpp b/src/net/socket_tcp.cpp similarity index 78% rename from src/net/socket_p2p.cpp rename to src/net/socket_tcp.cpp index 4a4412100..1689c7646 100644 --- a/src/net/socket_p2p.cpp +++ b/src/net/socket_tcp.cpp @@ -29,32 +29,34 @@ using namespace std::placeholders; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) -// P2P (read). +// TCP (read). // ---------------------------------------------------------------------------- +// Fixed-size read that completes only when the mutable_buffer is filled. -void socket::p2p_read(const asio::mutable_buffer& out, +void socket::tcp_read(const asio::mutable_buffer& out, count_handler&& handler) NOEXCEPT { boost::asio::dispatch(strand_, - std::bind(&socket::do_p2p_read, + std::bind(&socket::do_tcp_read, shared_from_this(), out, std::move(handler))); } // private -void socket::do_p2p_read(const asio::mutable_buffer& out, +void socket::do_tcp_read(const asio::mutable_buffer& out, const count_handler& handler) NOEXCEPT { BC_ASSERT(stranded()); async_read(out, handler); } -// P2P (write). +// TCP (write). // ---------------------------------------------------------------------------- +// Buffer is fully allocated before write, identical to ws_write. -void socket::p2p_write(const asio::const_buffer& in, +void socket::tcp_write(const asio::const_buffer& in, count_handler&& handler) NOEXCEPT { - raw_write(in, true, std::move(handler)); + ws_write(in, true, std::move(handler)); } BC_POP_WARNING() diff --git a/src/net/socket_ws.cpp b/src/net/socket_ws.cpp index 619e6dbeb..168fd2ea6 100644 --- a/src/net/socket_ws.cpp +++ b/src/net/socket_ws.cpp @@ -32,6 +32,47 @@ using namespace std::placeholders; BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) +// WS (read). +// ---------------------------------------------------------------------------- +// The flat_buffer's max_size() must be large enough to hold the complete ws +// framed message. The read will fail if the message exceeds this limit. + +void socket::ws_read(http::flat_buffer& out, + count_handler&& handler) NOEXCEPT +{ + boost::asio::dispatch(strand_, + std::bind(&socket::do_ws_read, + shared_from_this(), std::ref(out), std::move(handler))); +} + +// private +void socket::do_ws_read(ref out, + const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + async_read(out.get(), handler); +} + +// WS (write). +// ---------------------------------------------------------------------------- +// Buffer is fully allocated before write, identical to tcp_write. + +void socket::ws_write(const asio::const_buffer& in, bool binary, + count_handler&& handler) NOEXCEPT +{ + boost::asio::dispatch(strand_, + std::bind(&socket::do_ws_write, + shared_from_this(), in, binary, std::move(handler))); +} + +// private +void socket::do_ws_write(const asio::const_buffer& in, bool binary, + const count_handler& handler) NOEXCEPT +{ + BC_ASSERT(stranded()); + async_write(in, binary, handler); +} + // WS (event). // ---------------------------------------------------------------------------- // This is a unique/internal aspect of websockets. diff --git a/test/error.cpp b/test/error.cpp index 903e6a634..4db825d09 100644 --- a/test/error.cpp +++ b/test/error.cpp @@ -2149,15 +2149,6 @@ BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_params_not_collection__true_expected BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc params not collection"); } -BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_reader_bad_buffer__true_expected_message) -{ - constexpr auto value = error::jsonrpc_reader_bad_buffer; - const auto ec = code(value); - BOOST_REQUIRE(ec); - BOOST_REQUIRE(ec == value); - BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc reader bad buffer"); -} - BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_reader_stall__true_expected_message) { constexpr auto value = error::jsonrpc_reader_stall; diff --git a/test/messages/http_body_reader.cpp b/test/messages/http_body_reader.cpp index 71c02fc1d..5e7ef0e6c 100644 --- a/test/messages/http_body_reader.cpp +++ b/test/messages/http_body_reader.cpp @@ -40,12 +40,11 @@ struct accessor BOOST_AUTO_TEST_SUITE(http_body_reader_tests) -BOOST_AUTO_TEST_CASE(http_body_reader__init__bogus__constructs_empty_reader) +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_bogus__empty_reader) { message_header header{}; header.set(http::field::content_type, "bogus"); body::value_type value{}; - value = empty_body::value_type{}; accessor reader(header, value); length_type length{ max_size_t }; boost_code ec{}; @@ -53,12 +52,24 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__bogus__constructs_empty_reader) BOOST_REQUIRE(std::holds_alternative(reader.reader_)); } -BOOST_AUTO_TEST_CASE(http_body_reader__init__plain_json__constructs_json_reader) +BOOST_AUTO_TEST_CASE(http_body_reader__init__string_body_bogus__string_reader) +{ + message_header header{}; + header.set(http::field::content_type, "bogus"); + body::value_type value{}; + value = string_body::value_type{}; + accessor reader(header, value); + length_type length{ max_size_t }; + boost_code ec{}; + reader.init(length, ec); + BOOST_REQUIRE(std::holds_alternative(reader.reader_)); +} + +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_plain_application_json__constructs_json_reader) { message_header header{}; header.set(http::field::content_type, "application/json"); body::value_type value{}; - value = json_body::value_type{}; value.plain_json = true; accessor reader(header, value); length_type length{ max_size_t }; @@ -67,12 +78,11 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__plain_json__constructs_json_reader) BOOST_REQUIRE(std::holds_alternative(reader.reader_)); } -BOOST_AUTO_TEST_CASE(http_body_reader__init__rpc_json__constructs_rpc_reader) +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_not_plain_application_json__constructs_rpc_reader) { message_header header{}; header.set(http::field::content_type, "application/json"); body::value_type value{}; - value = json_body::value_type{}; value.plain_json = false; accessor reader(header, value); length_type length{ max_size_t }; @@ -81,13 +91,12 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__rpc_json__constructs_rpc_reader) BOOST_REQUIRE(std::holds_alternative(reader.reader_)); } -BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream__constructs_data_reader) +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream__constructs_data_reader) { message_header header{}; header.set(http::field::content_type, "application/octet-stream"); header.set(http::field::content_disposition, "bogus"); body::value_type value{}; - value = chunk_body::value_type{}; accessor reader(header, value); length_type length{ max_size_t }; boost_code ec{}; @@ -96,13 +105,12 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream__construct } // TODO: linux debug boost asserts on body_.file_.is_open(). -////BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream_with_attachment__constructs_file_reader) +////BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_attachment__constructs_file_reader) ////{ //// message_header header{}; //// header.set(http::field::content_type, "application/octet-stream"); //// header.set(http::field::content_disposition, "filename=somenonsense.jpg"); //// body::value_type value{}; -//// value = file_body::value_type{}; //// accessor reader(header, value); //// length_type length{ max_size_t }; //// boost_code ec{}; @@ -111,13 +119,12 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream__construct ////} // TODO: linux debug boost asserts on body_.file_.is_open(). -////BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream_with_dirty_attachment__constructs_file_reader) +////BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_application_octet_stream_with_dirty_attachment__constructs_file_reader) ////{ //// message_header header{}; //// header.set(http::field::content_type, "application/octet-stream"); //// header.set(http::field::content_disposition, "dirty 42; filename* = somenonsense.jpg; some other nonsense"); //// body::value_type value{}; -//// value = file_body::value_type{}; //// accessor reader(header, value); //// length_type length{ max_size_t }; //// boost_code ec{}; @@ -125,12 +132,11 @@ BOOST_AUTO_TEST_CASE(http_body_reader__init__application_octet_stream__construct //// BOOST_REQUIRE(std::holds_alternative(reader.reader_)); ////} -BOOST_AUTO_TEST_CASE(http_body_reader__init__text_plain__constructs_string_reader) +BOOST_AUTO_TEST_CASE(http_body_reader__init__default_body_text_plain__constructs_string_reader) { message_header header{}; header.set(http::field::content_type, "text/plain"); body::value_type value{}; - value = string_body::value_type{}; accessor reader(header, value); length_type length{ max_size_t }; boost_code ec{}; diff --git a/test/messages/rpc/body_reader.cpp b/test/messages/rpc/body_reader.cpp index c09ae872e..688074a3b 100644 --- a/test/messages/rpc/body_reader.cpp +++ b/test/messages/rpc/body_reader.cpp @@ -149,7 +149,7 @@ BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__simple_request_terminated_with_new BOOST_REQUIRE(body.model.is_null()); } -BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__simple_request_terminated_without_newline__success_not_done) +BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__simple_request_terminated_without_newline__need_more_not_done) { const std::string_view text{ R"({"jsonrpc":"2.0","id":1,"method":"test"})" }; const asio::const_buffer buffer{ text.data(), text.size() }; @@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(rpc_body_reader__finish__simple_request_terminated_without_ BOOST_REQUIRE(!ec); reader.finish(ec); - BOOST_REQUIRE(!ec); + BOOST_REQUIRE(ec == error::http_error_t::need_more); BOOST_REQUIRE(!reader.done()); } diff --git a/test/net/socket.cpp b/test/net/socket.cpp index f641ca9f5..de7b1c1fb 100644 --- a/test/net/socket.cpp +++ b/test/net/socket.cpp @@ -187,7 +187,7 @@ BOOST_AUTO_TEST_CASE(socket__read__disconnected__error) const auto instance = std::make_shared(log, pool.service(), std::move(params)); system::data_array<42> data; - instance->p2p_read({ data.data(), data.size() }, + instance->tcp_read({ data.data(), data.size() }, [instance](const code& ec, size_t size) { // 10009 (WSAEBADF, invalid file handle) gets mapped to bad_stream. @@ -213,7 +213,7 @@ BOOST_AUTO_TEST_CASE(socket__write__disconnected__bad_stream) const auto instance = std::make_shared(log, pool.service(), std::move(params)); system::data_array<42> data; - instance->p2p_write({ data.data(), data.size() }, + instance->tcp_write({ data.data(), data.size() }, [instance](const code& ec, size_t size) { // 10009 (WSAEBADF, invalid file handle) gets mapped to bad_stream.