Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions include/bitcoin/network/error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ enum error_t : uint8_t
exhausted_variants,
unknown_name,

// rpc error
// query string parse error
message_overflow,
undefined_type,
unexpected_method,
Expand All @@ -310,7 +310,17 @@ enum error_t : uint8_t
extra_named,
missing_array,
missing_object,
missing_parameter
missing_parameter,

// json-rpc error
jsonrpc_requires_method,
jsonrpc_v1_requires_params,
jsonrpc_v1_requires_array_params,
jsonrpc_v1_requires_id,
jsonrpc_reader_bad_buffer,
jsonrpc_reader_stall,
jsonrpc_reader_exception,
jsonrpc_writer_exception
};

// No current need for error_code equivalence mapping.
Expand Down Expand Up @@ -364,6 +374,9 @@ BCT_API code ws_to_error_code(const boost_code& ec) NOEXCEPT;
/// 1:1 mapping of boost::json::error to network (or error::unknown).
BCT_API code json_to_error_code(const boost_code& ec) NOEXCEPT;

/// 1:1 mapping of wrapped json-rpc codes back to network (or error::unknown).
BCT_API code rpc_to_error_code(const boost_code& ec) NOEXCEPT;

} // namespace error
} // namespace network
} // namespace libbitcoin
Expand Down
4 changes: 2 additions & 2 deletions include/bitcoin/network/impl/channels/channel_rpc.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ inline void CLASS::handle_receive(const code& ec, size_t bytes,
identity_ = request->message.id;
version_ = request->message.jsonrpc;

LOGA("Rpc request: (" << bytes << ") bytes from [" << endpoint() << "] "
LOGA("Rpc request : (" << bytes << ") bytes [" << endpoint() << "] "
<< request->message.method << "(...).");

reading_ = false;
Expand Down Expand Up @@ -187,7 +187,7 @@ inline void CLASS::handle_send(const code& ec, size_t bytes,
// Typically a noop, but handshake may pause channel here.
handler(ec);

LOGA("Rpc response: (" << bytes << ") bytes to [" << endpoint() << "] "
LOGA("Rpc response: (" << bytes << ") bytes [" << endpoint() << "] "
<< response->message.error.value_or(rpc::result_t{}).message);

// Continue read loop (does not unpause or restart channel).
Expand Down
20 changes: 10 additions & 10 deletions include/bitcoin/network/impl/messages/json_body.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,17 @@ size_t CLASS::reader::put(const buffer_type& buffer, boost_code& ec) NOEXCEPT
TEMPLATE
void CLASS::reader::finish(boost_code& ec) NOEXCEPT
{
using namespace network::error;
if (!done())
{
ec = to_http_code(http_error_t::partial_message);
return;
}
ec.clear();

BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
parser_.finish(ec);
BC_POP_WARNING()
// Calling parser.finish() when parser.done() results in error::incomplete.
if (!parser_.done())
{
BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
parser_.finish(ec);
BC_POP_WARNING()

if (ec) return;
if (ec) return;
}

try
{
Expand All @@ -114,6 +113,7 @@ 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);
}

Expand Down
5 changes: 3 additions & 2 deletions include/bitcoin/network/net/socket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,14 @@ class BCT_API socket
{
typedef std::shared_ptr<read_rpc> ptr;

read_rpc(rpc::request& request) NOEXCEPT
: value{ request }, reader{ value }
read_rpc(rpc::request& request, http::flat_buffer& buffer) NOEXCEPT
: value{ request }, reader{ value }, buffer{ buffer }
{
}

rpc::request& value;
rpc::reader reader;
http::flat_buffer& buffer;
};

struct write_rpc
Expand Down
36 changes: 31 additions & 5 deletions src/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ exhausted_variants, "exhausted variants" },
{ unknown_name, "unknown name" },

// rpc error
// query string parse error
{ message_overflow, "message overflow" },
{ undefined_type, "undefined type" },
{ unexpected_method, "unexpected method" },
Expand All @@ -281,7 +281,17 @@ DEFINE_ERROR_T_MESSAGE_MAP(error)
{ extra_named, "extra named" },
{ missing_array, "missing array" },
{ missing_object, "missing object" },
{ missing_parameter, "missing parameter" }
{ missing_parameter, "missing parameter" },

// json-rpc error
{ jsonrpc_requires_method, "jsonrpc requires method" },
{ jsonrpc_v1_requires_params, "jsonrpc v1 requires params" },
{ jsonrpc_v1_requires_array_params, "jsonrpc v1 requires array params" },
{ jsonrpc_v1_requires_id, "jsonrpc v1 requires id" },
{ 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" }
};

DEFINE_ERROR_T_CATEGORY(error, "network", "network code")
Expand All @@ -294,6 +304,7 @@ bool asio_is_canceled(const boost_code& ec) NOEXCEPT
|| ec == boost::asio::error::operation_aborted;
}

// TODO: change to cast model (like others).
// The success and operation_canceled codes are the only expected in normal
// operation, so these are first, to optimize the case where asio_is_canceled
// is not used. boost_code overloads the `==` operator to include category.
Expand Down Expand Up @@ -440,15 +451,30 @@ code ssl_to_error_code(const boost_code& ec) NOEXCEPT

switch (static_cast<asio_ssl_stream_error_t>(ec.value()))
{
case asio_ssl_stream_error_t::stream_truncated: return error::tls_stream_truncated;
case asio_ssl_stream_error_t::unspecified_system_error: return error::tls_unspecified_system_error;
case asio_ssl_stream_error_t::unexpected_result: return error::tls_unexpected_result;
case asio_ssl_stream_error_t::stream_truncated:
return error::tls_stream_truncated;
case asio_ssl_stream_error_t::unspecified_system_error:
return error::tls_unspecified_system_error;
case asio_ssl_stream_error_t::unexpected_result:
return error::tls_unexpected_result;

// TODO: return ssl category generic error.
default: return error::unknown;
}
}

// includes http codes
code rpc_to_error_code(const boost_code& ec) NOEXCEPT
{
if (!ec)
return error::success;

if (ec.category() == network::error::error_category::singleton)
return { static_cast<error_t>(ec.value()) };

return http_to_error_code(ec);
}

// includes json codes
code http_to_error_code(const boost_code& ec) NOEXCEPT
{
Expand Down
35 changes: 14 additions & 21 deletions src/messages/rpc/body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ put(const buffer_type& buffer, boost_code& ec) NOEXCEPT

if (is_null(buffer.data()))
{
ec = to_system_code(boost_error_t::bad_address);
ec = code{ error::jsonrpc_reader_bad_buffer };
return {};
}

Expand All @@ -67,7 +67,7 @@ put(const buffer_type& buffer, boost_code& ec) NOEXCEPT
// There is no terminator (terminal).
if (is_zero(parsed))
{
ec = to_http_code(http_error_t::end_of_stream);
ec = code{ error::jsonrpc_reader_stall };
return parsed;
}

Expand Down Expand Up @@ -115,33 +115,27 @@ finish(boost_code& ec) NOEXCEPT
}
catch (...)
{
// As a catch-all we blame alloc.
ec = to_http_code(http_error_t::bad_alloc);
ec = code{ error::jsonrpc_reader_exception };
}

// Post-parse semantic validation.

// Set version default.
if (value_.message.jsonrpc == version::undefined)
value_.message.jsonrpc = version::v1;

if (value_.message.method.empty() ||
!value_.message.params.has_value())
// Post-parse semantic validation.
if (value_.message.method.empty())
{
ec = to_system_code(boost_error_t::bad_message);
return;
ec = code{ error::jsonrpc_requires_method };
}

if (value_.message.jsonrpc == version::v1)
else if (value_.message.jsonrpc == version::v1)
{
if (!value_.message.id.has_value())
ec = to_system_code(boost_error_t::bad_message);
if (!value_.message.params.has_value())
ec = code{ error::jsonrpc_v1_requires_params };
else if (!value_.message.id.has_value())
ec = code{ error::jsonrpc_v1_requires_id };
else if (!std::holds_alternative<array_t>(
value_.message.params.value()))
ec = to_system_code(boost_error_t::bad_message);

// TODO: v1 batch is not allowed.
////else if (value_.message.is_batch())
//// ec = to_system_code(boost_error_t::bad_message);
ec = code{ error::jsonrpc_v1_requires_array_params };
}
}

Expand Down Expand Up @@ -190,8 +184,7 @@ init(boost_code& ec) NOEXCEPT
}
catch (...)
{
// As a catch-all we blame alloc.
ec = to_http_code(http_error_t::bad_alloc);
ec = code{ error::jsonrpc_writer_exception };
return;
}

Expand Down
19 changes: 9 additions & 10 deletions src/net/socket_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ void socket::rpc_read(http::flat_buffer& buffer, rpc::request& request,
count_handler&& handler) NOEXCEPT
{
boost_code ec{};
const auto in = emplace_shared<read_rpc>(request);
in->value.buffer = emplace_shared<http::flat_buffer>(buffer);
const auto in = emplace_shared<read_rpc>(request, buffer);
in->reader.init({}, ec);

boost::asio::dispatch(strand_,
Expand All @@ -60,15 +59,15 @@ void socket::do_rpc_read(boost_code ec, size_t total, const read_rpc::ptr& in,

if (ec)
{
// Json parser emits http and json codes.
const auto code = error::http_to_error_code(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;
}

VARIANT_DISPATCH_METHOD(get_tcp(),
async_read_some(in->value.buffer->prepare(size),
async_read_some(in->buffer.prepare(size),
std::bind(&socket::handle_rpc_read,
shared_from_this(), _1, _2, total, in, handler)));
}
Expand All @@ -93,12 +92,12 @@ void socket::handle_rpc_read(boost_code ec, size_t size, size_t total,

if (!ec)
{
in->value.buffer->commit(size);
const auto data = in->value.buffer->data();
in->buffer.commit(size);
const auto data = in->buffer.data();
const auto parsed = in->reader.put(data, ec);
if (!ec)
{
in->value.buffer->consume(parsed);
in->buffer.consume(parsed);
if (in->reader.done())
{
in->reader.finish(ec);
Expand Down Expand Up @@ -138,8 +137,8 @@ void socket::do_rpc_write(boost_code ec, size_t total,
const auto buffer = ec ? write_rpc::out_buffer{} : out->writer.get(ec);
if (ec)
{
// Json serializer emits http and json codes.
const auto code = error::http_to_error_code(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;
Expand Down
76 changes: 75 additions & 1 deletion test/error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1954,7 +1954,7 @@ BOOST_AUTO_TEST_CASE(error_t__code__unknown_name__true_expected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "unknown name");
}

// rpc error
// query string parse error

BOOST_AUTO_TEST_CASE(error_t__code__message_overflow__true_expected_message)
{
Expand Down Expand Up @@ -2037,4 +2037,78 @@ BOOST_AUTO_TEST_CASE(error_t__code__missing_parameter__true_expected_message)
BOOST_REQUIRE_EQUAL(ec.message(), "missing parameter");
}

// json-rpc error

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_requires_method__true_expected_message)
{
constexpr auto value = error::jsonrpc_requires_method;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc requires method");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_v1_requires_params__true_expected_message)
{
constexpr auto value = error::jsonrpc_v1_requires_params;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc v1 requires params");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_v1_requires_array_params__true_expected_message)
{
constexpr auto value = error::jsonrpc_v1_requires_array_params;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc v1 requires array params");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_v1_requires_id__true_expected_message)
{
constexpr auto value = error::jsonrpc_v1_requires_id;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc v1 requires id");
}

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;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc reader stall");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_reader_exception__true_expected_message)
{
constexpr auto value = error::jsonrpc_reader_exception;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc reader exception");
}

BOOST_AUTO_TEST_CASE(error_t__code__jsonrpc_writer_exception__true_expected_message)
{
constexpr auto value = error::jsonrpc_writer_exception;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "jsonrpc writer exception");
}

BOOST_AUTO_TEST_SUITE_END()
Loading