From e1bd9541f18641cda03b076285b54458599f57a8 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Wed, 7 Jun 2023 09:01:46 -0400 Subject: [PATCH] Update ::wire:: to be closer to Monero variant (#70) --- src/admin_main.cpp | 5 +- src/db/storage.cpp | 8 +- src/lmdb/msgpack_table.h | 15 +- src/rest_server.cpp | 33 ++-- src/rpc/CMakeLists.txt | 2 +- src/rpc/admin.cpp | 10 +- src/rpc/daemon_pub.cpp | 6 +- src/rpc/daemon_zmq.cpp | 23 ++- src/rpc/light_wallet.cpp | 7 +- src/rpc/rates.cpp | 6 +- src/scanner.cpp | 15 +- src/wire/CMakeLists.txt | 3 +- src/wire/crypto.h | 53 +---- src/wire/field.h | 160 +++++---------- src/wire/json/base.h | 22 ++- src/wire/json/fwd.h | 1 + src/wire/json/read.h | 24 --- src/wire/json/write.cpp | 5 +- src/wire/json/write.h | 62 ++---- src/wire/msgpack/base.h | 18 +- src/wire/msgpack/fwd.h | 1 + src/wire/msgpack/read.h | 24 --- src/wire/msgpack/write.cpp | 4 +- src/wire/msgpack/write.h | 62 +----- src/wire/read.h | 140 +++++-------- src/wire/traits.h | 45 ++++- src/wire/wrapper/CMakeLists.txt | 35 ++++ src/wire/wrapper/array.h | 158 +++++++++++++++ src/wire/wrapper/variant.cpp | 40 ++++ src/wire/wrapper/variant.h | 207 ++++++++++++++++++++ src/wire/wrappers_impl.h | 62 ++++++ src/wire/write.h | 206 +++++++++++-------- tests/unit/rpc/admin.test.cpp | 13 +- tests/unit/wire/json/read.write.test.cpp | 22 ++- tests/unit/wire/msgpack/read.write.test.cpp | 41 ++-- tests/unit/wire/read.write.test.cpp | 41 ++-- 36 files changed, 992 insertions(+), 587 deletions(-) create mode 100644 src/wire/wrapper/CMakeLists.txt create mode 100644 src/wire/wrapper/array.h create mode 100644 src/wire/wrapper/variant.cpp create mode 100644 src/wire/wrapper/variant.h create mode 100644 src/wire/wrappers_impl.h diff --git a/src/admin_main.cpp b/src/admin_main.cpp index 52c78c7..a114014 100644 --- a/src/admin_main.cpp +++ b/src/admin_main.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,8 @@ #include "wire/crypto.h" #include "wire/filters.h" #include "wire/json/write.h" +#include "wire/wrapper/array.h" +#include "wire/wrappers_impl.h" namespace { @@ -79,7 +82,7 @@ namespace const auto transform = [] (lws::db::account src) { return admin_display{std::move(src)}; }; - wire::array(dest, (source.value | boost::adaptors::filtered(filter)), transform); + wire_write::bytes(dest, wire::array(source.value | boost::adaptors::filtered(filter) | boost::adaptors::transformed(transform))); } template diff --git a/src/db/storage.cpp b/src/db/storage.cpp index 041e455..6463a1b 100644 --- a/src/db/storage.cpp +++ b/src/db/storage.cpp @@ -59,6 +59,8 @@ #include "wire/filters.h" #include "wire/json.h" #include "wire/vector.h" +#include "wire/wrapper/array.h" +#include "wire/wrappers_impl.h" namespace lws { @@ -746,7 +748,7 @@ namespace db { wire::object(dest, wire::field("scan_height", self.first), - wire::field("accounts", wire::as_array(std::move(self.second))) + wire::field("accounts", wire::array(std::move(self.second))) ); } @@ -839,10 +841,10 @@ namespace db const wire::as_array_filter toggle_keys_filter{{show_keys}}; wire::json_stream_writer json_stream{out}; wire::object(json_stream, - wire::field(blocks.name, wire::as_array(reverse(*blocks_partial))), + wire::field(blocks.name, wire::array(reverse(*blocks_partial))), wire::field(accounts.name, wire::as_object(accounts_stream->make_range(), wire::enum_as_string, toggle_keys_filter)), wire::field(accounts_by_address.name, wire::as_object(transform(accounts_ba_stream->make_range(), address_as_key))), - wire::field(accounts_by_height.name, wire::as_array(accounts_bh_stream->make_range())), + wire::field(accounts_by_height.name, wire::array(accounts_bh_stream->make_range())), wire::field(outputs.name, wire::as_object(outputs_stream->make_range(), wire::as_integer, wire::as_array)), wire::field(spends.name, wire::as_object(spends_stream->make_range(), wire::as_integer, wire::as_array)), wire::field(images.name, wire::as_object(images_stream->make_range(), output_id_key{}, wire::as_array)), diff --git a/src/lmdb/msgpack_table.h b/src/lmdb/msgpack_table.h index 72217db..239dceb 100644 --- a/src/lmdb/msgpack_table.h +++ b/src/lmdb/msgpack_table.h @@ -37,7 +37,10 @@ namespace lmdb { epee::byte_stream initial; initial.write({reinterpret_cast(std::addressof(val1)), sizeof(val1)}); - return wire_write::to_bytes(wire::msgpack_slice_writer{std::move(initial), true}, val2); + + wire::msgpack_slice_writer dest{std::move(initial), true}; + wire_write::bytes(dest, val2); + return epee::byte_slice{dest.take_sink()}; } /*! @@ -76,10 +79,12 @@ namespace lmdb auto msgpack_bytes = lmdb::to_byte_span(value); msgpack_bytes.remove_prefix(sizeof(out.first)); - auto msgpack = wire::msgpack::from_bytes(epee::byte_slice{{msgpack_bytes}}); - if (!msgpack) - return msgpack.error(); - out.second = std::move(*msgpack); + + msgpack_value_type second{}; + const std::error_code error = wire::msgpack::from_bytes(epee::byte_slice{{msgpack_bytes}}, second); + if (error) + return error; + out.second = std::move(second); return out; } diff --git a/src/rest_server.cpp b/src/rest_server.cpp index 2d90994..6329592 100644 --- a/src/rest_server.cpp +++ b/src/rest_server.cpp @@ -661,14 +661,19 @@ namespace lws using request = typename E::request; using response = typename E::response; - expect req = wire::json::from_bytes(std::move(root)); - if (!req) - return req.error(); + request req{}; + std::error_code error = wire::json::from_bytes(std::move(root), req); + if (error) + return error; - expect resp = E::handle(std::move(*req), std::move(disk), gclient); + expect resp = E::handle(std::move(req), std::move(disk), gclient); if (!resp) return resp.error(); - return wire::json::to_bytes(*resp); + + epee::byte_slice out{}; + if ((error = wire::json::to_bytes(out, *resp))) + return error; + return {std::move(out)}; } template @@ -693,17 +698,21 @@ namespace lws expect call_admin(std::string&& root, db::storage disk, const rpc::client&, const bool disable_auth) { using request = typename E::request; - expect> req = wire::json::from_bytes>(std::move(root)); - if (!req) - return req.error(); + + admin req{}; + { + const std::error_code error = wire::json::from_bytes(std::move(root), req); + if (error) + return error; + } if (!disable_auth) { - if (!req->auth) + if (!req.auth) return {error::account_not_found}; db::account_address address{}; - if (!crypto::secret_key_to_public_key(*(req->auth), address.view_public)) + if (!crypto::secret_key_to_public_key(*(req.auth), address.view_public)) return {error::crypto_failure}; auto reader = disk.start_read(); @@ -719,8 +728,8 @@ namespace lws } wire::json_slice_writer dest{}; - MONERO_CHECK(E{}(dest, std::move(disk), std::move(req->params))); - return dest.take_bytes(); + MONERO_CHECK(E{}(dest, std::move(disk), std::move(req.params))); + return epee::byte_slice{dest.take_sink()}; } struct endpoint diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index ebf9fac..1d55fd2 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -30,4 +30,4 @@ set(monero-lws-rpc_sources admin.cpp client.cpp daemon_pub.cpp daemon_zmq.cpp li set(monero-lws-rpc_headers admin.h client.h daemon_pub.h daemon_zmq.h fwd.h json.h light_wallet.h rates.h) add_library(monero-lws-rpc ${monero-lws-rpc_sources} ${monero-lws-rpc_headers}) -target_link_libraries(monero-lws-rpc monero::libraries monero-lws-wire-json) +target_link_libraries(monero-lws-rpc monero::libraries monero-lws-wire-json monero-lws-wire-wrapper) diff --git a/src/rpc/admin.cpp b/src/rpc/admin.cpp index 1563507..7ad83a9 100644 --- a/src/rpc/admin.cpp +++ b/src/rpc/admin.cpp @@ -27,6 +27,7 @@ #include "admin.h" +#include #include #include #include @@ -41,6 +42,8 @@ #include "wire/traits.h" #include "wire/uuid.h" #include "wire/vector.h" +#include "wire/wrapper/array.h" +#include "wire/wrappers_impl.h" namespace wire { @@ -94,7 +97,7 @@ namespace void write_bytes(wire::json_writer& dest, const truncated>> self) { const auto truncate = [] (V src) { return truncated{std::move(src)}; }; - wire::array(dest, std::move(self.value), truncate); + wire_write::bytes(dest, wire::array(boost::adaptors::transform(std::move(self.value), truncate))); } template @@ -127,7 +130,10 @@ namespace void write_addresses(wire::writer& dest, epee::span self) { // writes an array of monero base58 address strings - wire::object(dest, wire::field("updated", wire::as_array(self, lws::db::address_string))); + + wire::object(dest, + wire::field("updated", wire::array(boost::adaptors::transform(self, lws::db::address_string))) + ); } expect write_addresses(wire::writer& dest, const expect>& self) diff --git a/src/rpc/daemon_pub.cpp b/src/rpc/daemon_pub.cpp index dcee5ed..1bb343f 100644 --- a/src/rpc/daemon_pub.cpp +++ b/src/rpc/daemon_pub.cpp @@ -77,7 +77,11 @@ namespace rpc expect minimal_chain_pub::from_json(std::string&& source) { - return wire::json::from_bytes(std::move(source)); + minimal_chain_pub out{}; + std::error_code err = wire::json::from_bytes(std::move(source), out); + if (err) + return err; + return {std::move(out)}; } } } diff --git a/src/rpc/daemon_zmq.cpp b/src/rpc/daemon_zmq.cpp index e8c3944..5ef55c8 100644 --- a/src/rpc/daemon_zmq.cpp +++ b/src/rpc/daemon_zmq.cpp @@ -32,6 +32,7 @@ #include "rpc/message_data_structs.h" // monero/src #include "wire/crypto.h" #include "wire/json.h" +#include "wire/wrapper/variant.h" #include "wire/vector.h" namespace @@ -103,14 +104,13 @@ namespace cryptonote } static void read_bytes(wire::json_reader& source, tx_out& self) { + auto variant = wire::variant(std::ref(self.target)); wire::object(source, WIRE_FIELD(amount), - wire::variant_field("transaction output variant", std::ref(self.target), - wire::option{"to_key"}, - wire::option{"to_tagged_key"}, - wire::option{"to_script"}, - wire::option{"to_scripthash"} - ) + WIRE_OPTION("to_key", txout_to_key, variant), + WIRE_OPTION("to_tagged_key", txout_to_tagged_key, variant), + WIRE_OPTION("to_script", txout_to_script, variant), + WIRE_OPTION("to_scripthash", txout_to_scripthash, variant) ); } @@ -132,13 +132,12 @@ namespace cryptonote } static void read_bytes(wire::json_reader& source, txin_v& self) { + auto variant = wire::variant(std::ref(self)); wire::object(source, - wire::variant_field("transaction input variant", std::ref(self), - wire::option{"to_key"}, - wire::option{"gen"}, - wire::option{"to_script"}, - wire::option{"to_scripthash"} - ) + WIRE_OPTION("to_key", txin_to_key, variant), + WIRE_OPTION("gen", txin_gen, variant), + WIRE_OPTION("to_script", txin_to_script, variant), + WIRE_OPTION("to_scripthash", txin_to_scripthash, variant) ); } diff --git a/src/rpc/light_wallet.cpp b/src/rpc/light_wallet.cpp index b6c04df..aa49b63 100644 --- a/src/rpc/light_wallet.cpp +++ b/src/rpc/light_wallet.cpp @@ -28,6 +28,7 @@ #include "light_wallet.h" #include +#include #include #include #include @@ -44,6 +45,8 @@ #include "wire/json.h" #include "wire/traits.h" #include "wire/vector.h" +#include "wire/wrapper/array.h" +#include "wire/wrappers_impl.h" namespace { @@ -261,7 +264,7 @@ namespace lws WIRE_FIELD_COPY(start_height), WIRE_FIELD_COPY(transaction_height), WIRE_FIELD_COPY(blockchain_height), - wire::field("transactions", wire::as_array(boost::adaptors::index(self.transactions))) + wire::field("transactions", wire::array(boost::adaptors::index(self.transactions))) ); } @@ -297,7 +300,7 @@ namespace lws WIRE_FIELD_COPY(per_byte_fee), WIRE_FIELD_COPY(fee_mask), WIRE_FIELD_COPY(amount), - wire::field("outputs", wire::as_array(std::cref(self.outputs), expand)) + wire::field("outputs", wire::array(boost::adaptors::transform(self.outputs, expand))) ); } diff --git a/src/rpc/rates.cpp b/src/rpc/rates.cpp index 399265d..51ddab8 100644 --- a/src/rpc/rates.cpp +++ b/src/rpc/rates.cpp @@ -72,7 +72,11 @@ namespace lws expect crypto_compare_::operator()(std::string&& body) const { - return wire::json::from_bytes(std::move(body)); + lws::rates out{}; + const std::error_code error = wire::json::from_bytes(std::move(body), out); + if (error) + return error; + return {std::move(out)}; } } // rpc } // lws diff --git a/src/scanner.cpp b/src/scanner.cpp index 3d81350..545e3d0 100644 --- a/src/scanner.cpp +++ b/src/scanner.cpp @@ -158,9 +158,15 @@ namespace lws if (uri.empty()) uri = "/"; + epee::byte_slice bytes{}; const std::string& url = event.value.second.url; - const epee::byte_slice bytes = wire::json::to_bytes(event); + const std::error_code json_error = wire::json::to_bytes(bytes, event); const net::http::http_response_info* info = nullptr; + if (json_error) + { + MERROR("Failed to generate webhook JSON: " << json_error.message()); + return; + } MINFO("Sending webhook to " << url); if (!client.invoke(uri, "POST", std::string{bytes.begin(), bytes.end()}, timeout, std::addressof(info), params)) @@ -460,7 +466,12 @@ namespace lws MONERO_THROW(resp.error(), "Failed to retrieve blocks from daemon"); } - auto fetched = MONERO_UNWRAP(wire::json::from_bytes::response>(std::move(*resp))); + rpc::json::response fetched{}; + { + const std::error_code error = wire::json::from_bytes(std::move(*resp), fetched); + if (error) + throw std::system_error{error}; + } if (fetched.result.blocks.empty()) throw std::runtime_error{"Daemon unexpectedly returned zero blocks"}; diff --git a/src/wire/CMakeLists.txt b/src/wire/CMakeLists.txt index a1cc14d..15fd61b 100644 --- a/src/wire/CMakeLists.txt +++ b/src/wire/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2020, The Monero Project +# Copyright (c) 2020-2023, The Monero Project # # All rights reserved. # @@ -35,3 +35,4 @@ target_link_libraries(monero-lws-wire PRIVATE monero::libraries) add_subdirectory(json) add_subdirectory(msgpack) +add_subdirectory(wrapper) diff --git a/src/wire/crypto.h b/src/wire/crypto.h index 40ca03e..34e42f5 100644 --- a/src/wire/crypto.h +++ b/src/wire/crypto.h @@ -45,48 +45,13 @@ namespace crypto namespace wire { - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; - - template<> - struct is_blob - : std::true_type - {}; + WIRE_DECLARE_BLOB(crypto::ec_scalar); + WIRE_DECLARE_BLOB(crypto::hash); + WIRE_DECLARE_BLOB(crypto::hash8); + WIRE_DECLARE_BLOB(crypto::key_derivation); + WIRE_DECLARE_BLOB(crypto::key_image); + WIRE_DECLARE_BLOB(crypto::public_key); + WIRE_DECLARE_BLOB(crypto::signature); + WIRE_DECLARE_BLOB(crypto::view_tag); + WIRE_DECLARE_BLOB(rct::key); } diff --git a/src/wire/field.h b/src/wire/field.h index e05ae30..eda5c75 100644 --- a/src/wire/field.h +++ b/src/wire/field.h @@ -55,46 +55,70 @@ namespace wire { - template - struct unwrap_reference + /*! Links `name` to a `value` and index `I` for object serialization. + + `value_type` is `T` with optional `std::reference_wrapper` removed. + `value_type` needs a `read_bytes` function when parsing with a + `wire::reader` - see `read.h` for more info. `value_type` needs a + `write_bytes` function when parsing with a `wire::writer` - see `write.h` + for more info. + + Any `value_type` where `is_optional_on_empty == true`, will + automatically be converted to an optional field iff `value_type` has an + `empty()` method that returns `true`. The old output engine omitted fields + when an array was empty, and the standard input macro would ignore the + `false` return for the missing field. For compability reasons, the + input/output engine here matches that behavior. See `wrapper/array.h` to + enforce a required field even when the array is empty or specialize the + `is_optional_on_empty` trait. Only new fields should use this behavior. + + Additional concept requirements for `value_type` when `Required == false`: + * must have an `operator*()` function. + * must have a conversion to bool function that returns true when + `operator*()` is safe to call (and implicitly when the associated field + should be written as opposed to skipped/omitted). + Additional concept requirements for `value_type` when `Required == false` + when reading: + * must have an `emplace()` method that ensures `operator*()` is safe to call. + * must have a `reset()` method to indicate a field was skipped/omitted. + + If a standard type needs custom serialization, one "trick": + ``` + struct custom_tag{}; + void read_bytes(wire::reader&, boost::fusion::pair) + { ... } + void write_bytes(wire::writer&, boost::fusion::pair) + { ... } + + template + void object_map(F& format, T& self) { - using type = T; - }; + wire::object(format, + wire::field("foo", boost::fusion::make_pair(std::ref(self.foo))) + ); + } + ``` - template - struct unwrap_reference> - { - using type = T; - }; - - - //! Links `name` to a `value` for object serialization. + Basically each input/output format needs a unique type so that the compiler + knows how to "dispatch" the read/write calls. */ template struct field_ { - using value_type = typename unwrap_reference::type; - static constexpr bool is_required() noexcept { return Required; } - static constexpr std::size_t count() noexcept { return 1; } - static constexpr unsigned id() noexcept { return I; } + using value_type = unwrap_reference_t; //! \return True if field is forced optional when `get_value().empty()`. static constexpr bool optional_on_empty() noexcept { return is_optional_on_empty::value; } + static constexpr bool is_required() noexcept { return Required; } + static constexpr std::size_t count() noexcept { return 1; } + static constexpr unsigned id() noexcept { return I; } + const char* name; T value; - //! \return `value` with `std::reference_wrapper` removed. - constexpr const value_type& get_value() const noexcept - { - return value; - } - - //! \return `value` with `std::reference_wrapper` removed. - value_type& get_value() noexcept - { - return value; - } + constexpr const value_type& get_value() const noexcept { return value; } + value_type& get_value() noexcept { return value; } }; //! Links `name` to `value`. Use `std::ref` if de-serializing. @@ -112,76 +136,6 @@ namespace wire } - //! Links `name` to a type `T` for variant serialization. - template - struct option - { - static constexpr unsigned id() noexcept { return 0; } - const char* name; - }; - - //! \return Name associated with type `T` for variant `field`. - template - constexpr const char* get_option_name(const U& field) noexcept - { - return static_cast< const option& >(field).name; - } - - //! Links each type in a variant to a string key. - template - struct variant_field_ : option... - { - using value_type = typename unwrap_reference::type; - static constexpr bool is_required() noexcept { return Required; } - static constexpr std::size_t count() noexcept { return sizeof...(U); } - - constexpr variant_field_(const char* name, T value, option... opts) - : option(std::move(opts))..., name(name), value(std::move(value)) - {} - - const char* name; - T value; - - constexpr const value_type& get_value() const noexcept - { - return value; - } - - value_type& get_value() noexcept - { - return value; - } - - template - struct wrap - { - using result_type = void; - - variant_field_ self; - V visitor; - - template - void operator()(const X& value) const - { - visitor(get_option_name(self), value); - } - }; - - template - void visit(V visitor) const - { - apply_visitor(wrap{*this, std::move(visitor)}, get_value()); - } - }; - - //! Links variant `value` to a unique name per type in `opts`. Use `std::ref` for `value` if de-serializing. - template - constexpr inline variant_field_ variant_field(const char* name, T value, option... opts) - { - return {name, std::move(value), std::move(opts)...}; - } - - //! Indicates a field value should be written as an array template struct as_array_ @@ -260,6 +214,8 @@ namespace wire template inline constexpr bool available(const field_& elem) noexcept { + /* The old output engine always skipped fields when it was an empty array, + this follows that behavior. See comments for `field_`. */ return elem.is_required() || (elem.optional_on_empty() && !wire::empty(elem.get_value())); } template @@ -267,15 +223,5 @@ namespace wire { return bool(elem.get_value()); } - template - inline constexpr bool available(const variant_field_&) noexcept - { - return true; - } - template - inline constexpr bool available(const variant_field_& elem) - { - return elem != nullptr; - } } diff --git a/src/wire/json/base.h b/src/wire/json/base.h index 5027ffb..9020bb3 100644 --- a/src/wire/json/base.h +++ b/src/wire/json/base.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, The Monero Project +// Copyright (c) 2022-2023, The Monero Project // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are @@ -28,23 +28,31 @@ #pragma once #include +#include -#include "byte_slice.h" -#include "common/expect.h" +#include "byte_stream.h" #include "wire/json/fwd.h" +#include "wire/read.h" +#include "wire/write.h" namespace wire { struct json { using input_type = json_reader; - using output_type = json_writer; + using output_type = json_slice_writer; template - static expect from_bytes(std::string&& source); + static std::error_code from_bytes(std::string&& source, T& dest) + { + return wire_read::from_bytes(std::move(source), dest); + } - template - static epee::byte_slice to_bytes(const T& source); + template + static std::error_code to_bytes(T& dest, const U& source) + { + return wire_write::to_bytes(dest, source); + } }; } diff --git a/src/wire/json/fwd.h b/src/wire/json/fwd.h index b0f8032..bf82621 100644 --- a/src/wire/json/fwd.h +++ b/src/wire/json/fwd.h @@ -40,6 +40,7 @@ namespace wire { struct json; class json_reader; + class json_slice_writer; class json_writer; } diff --git a/src/wire/json/read.h b/src/wire/json/read.h index a9bfd7e..b17dea2 100644 --- a/src/wire/json/read.h +++ b/src/wire/json/read.h @@ -107,28 +107,4 @@ namespace wire \return True if another value to read. */ bool key(epee::span map, std::size_t&, std::size_t& index) override final; }; - - - // Don't call `read` directly in this namespace, do it from `wire_read`. - - template - expect json::from_bytes(std::string&& bytes) - { - json_reader source{std::move(bytes)}; - return wire_read::to(source); - } - - // specialization prevents type "downgrading" to base type in cpp files - - template - inline void array(json_reader& source, T& dest) - { - wire_read::array(source, dest); - } - - template - inline void object(json_reader& source, T... fields) - { - wire_read::object(source, wire_read::tracker{std::move(fields)}...); - } } // wire diff --git a/src/wire/json/write.cpp b/src/wire/json/write.cpp index d852d7f..8a4e1b8 100644 --- a/src/wire/json/write.cpp +++ b/src/wire/json/write.cpp @@ -54,10 +54,11 @@ namespace wire if (!formatter_.IsComplete()) throw std::logic_error{"json_writer::take_json() failed with incomplete JSON tree"}; } - epee::byte_slice json_writer::take_json() + epee::byte_stream json_writer::take_json() { check_complete(); - epee::byte_slice out{std::move(bytes_)}; + epee::byte_stream out{std::move(bytes_)}; + bytes_.clear(); formatter_.Reset(bytes_); return out; } diff --git a/src/wire/json/write.h b/src/wire/json/write.h index ee8ad40..31dd05a 100644 --- a/src/wire/json/write.h +++ b/src/wire/json/write.h @@ -60,15 +60,15 @@ namespace wire void check_flush(); protected: - json_writer(bool needs_flush) - : writer(), bytes_(), formatter_(bytes_), needs_flush_(needs_flush) + json_writer(epee::byte_stream&& out, bool needs_flush) + : writer(), bytes_(std::move(out)), formatter_(bytes_), needs_flush_(needs_flush) {} //! \throw std::logic_error if incomplete JSON tree void check_complete(); //! \throw std::logic_error if incomplete JSON tree. \return JSON bytes - epee::byte_slice take_json(); + epee::byte_stream take_json(); //! Flush bytes in local buffer to `do_flush(...)` void flush() @@ -78,6 +78,9 @@ namespace wire } public: + //! JSON does not need array sizes. + static constexpr std::false_type need_array_size() noexcept { return{}; } + json_writer(const json_writer&) = delete; virtual ~json_writer() noexcept; json_writer& operator=(const json_writer&) = delete; @@ -113,12 +116,18 @@ namespace wire //! Buffers entire JSON message in memory struct json_slice_writer final : json_writer { + using sink = epee::byte_stream; + + explicit json_slice_writer(sink&& out) + : json_writer(std::move(out), false) + {} + explicit json_slice_writer() - : json_writer(false) + : json_writer(epee::byte_stream{}, false) {} //! \throw std::logic_error if incomplete JSON tree \return JSON bytes - epee::byte_slice take_bytes() + epee::byte_stream take_sink() { return json_writer::take_json(); } @@ -132,7 +141,7 @@ namespace wire virtual void do_flush(epee::span) override final; public: explicit json_stream_writer(std::ostream& dest) - : json_writer(true), dest(dest) + : json_writer(epee::byte_stream{}, true), dest(dest) {} //! Flush remaining bytes to stream \throw std::logic_error if incomplete JSON tree @@ -142,45 +151,4 @@ namespace wire flush(); } }; - - template - epee::byte_slice json::to_bytes(const T& source) - { - return wire_write::to_bytes(source); - } - - template - inline void array(json_writer& dest, const T& source, F filter = F{}) - { - // works with "lazily" computed ranges - wire_write::array(dest, source, 0, std::move(filter)); - } - template - inline void write_bytes(json_writer& dest, as_array_ source) - { - wire::array(dest, source.get_value(), std::move(source.filter)); - } - template - inline enable_if::value> write_bytes(json_writer& dest, const T& source) - { - wire::array(dest, source); - } - - template - inline void dynamic_object(json_writer& dest, const T& source, F key_filter = F{}, G value_filter = G{}) - { - // works with "lazily" computed ranges - wire_write::dynamic_object(dest, source, 0, std::move(key_filter), std::move(value_filter)); - } - template - inline void write_bytes(json_writer& dest, as_object_ source) - { - wire::dynamic_object(dest, source.get_map(), std::move(source.key_filter), std::move(source.value_filter)); - } - - template - inline void object(json_writer& dest, T... fields) - { - wire_write::object(dest, std::move(fields)...); - } } diff --git a/src/wire/msgpack/base.h b/src/wire/msgpack/base.h index e3ac61f..159d975 100644 --- a/src/wire/msgpack/base.h +++ b/src/wire/msgpack/base.h @@ -36,13 +36,15 @@ #include "byte_slice.h" #include "common/expect.h" #include "wire/msgpack/fwd.h" +#include "wire/read.h" +#include "wire/write.h" namespace wire { struct msgpack { using input_type = msgpack_reader; - using output_type = msgpack_writer; + using output_type = msgpack_slice_writer; //! Tags that do not require bitmask to identify enum class tag : std::uint8_t @@ -162,12 +164,18 @@ namespace wire using object16 = type; using object32 = type; using object_types = std::tuple; - + template - static expect from_bytes(epee::byte_slice&& source); + static std::error_code from_bytes(epee::byte_slice&& source, T& dest) + { + return wire_read::from_bytes(std::move(source), dest); + } - template - static epee::byte_slice to_bytes(const T& source); + template + static std::error_code to_bytes(T& dest, const U& source) + { + return wire_write::to_bytes(dest, source); + } }; } diff --git a/src/wire/msgpack/fwd.h b/src/wire/msgpack/fwd.h index 9363a73..d26f9ec 100644 --- a/src/wire/msgpack/fwd.h +++ b/src/wire/msgpack/fwd.h @@ -40,6 +40,7 @@ namespace wire { struct msgpack; class msgpack_reader; + class msgpack_slice_writer; class msgpack_writer; } diff --git a/src/wire/msgpack/read.h b/src/wire/msgpack/read.h index 33fc627..67311eb 100644 --- a/src/wire/msgpack/read.h +++ b/src/wire/msgpack/read.h @@ -137,28 +137,4 @@ namespace wire \return True if another value to read. */ bool key(epee::span map, std::size_t&, std::size_t& index) override final; }; - - - // Don't call `read` directly in this namespace, do it from `wire_read`. - - template - expect msgpack::from_bytes(epee::byte_slice&& bytes) - { - msgpack_reader source{std::move(bytes)}; - return wire_read::to(source); - } - - // specialization prevents type "downgrading" to base type in cpp files - - template - inline void array(msgpack_reader& source, T& dest) - { - wire_read::array(source, dest); - } - - template - inline void object(msgpack_reader& source, T... fields) - { - wire_read::object(source, wire_read::tracker{std::move(fields)}...); - } } // wire diff --git a/src/wire/msgpack/write.cpp b/src/wire/msgpack/write.cpp index 17197b4..43930f6 100644 --- a/src/wire/msgpack/write.cpp +++ b/src/wire/msgpack/write.cpp @@ -147,10 +147,10 @@ namespace wire if (expected_) throw std::logic_error{"msgpack_writer::take_msgpack() failed with incomplete tree"}; } - epee::byte_slice msgpack_writer::take_msgpack() + epee::byte_stream msgpack_writer::take_msgpack() { check_complete(); - epee::byte_slice out{std::move(bytes_)}; + epee::byte_stream out{std::move(bytes_)}; bytes_.clear(); return out; } diff --git a/src/wire/msgpack/write.h b/src/wire/msgpack/write.h index 2467e68..eaefb17 100644 --- a/src/wire/msgpack/write.h +++ b/src/wire/msgpack/write.h @@ -86,19 +86,15 @@ namespace wire } protected: - msgpack_writer(epee::byte_stream&& initial, bool integer_keys, bool needs_flush) - : writer(), bytes_(std::move(initial)), expected_(1), integer_keys_(integer_keys), needs_flush_(needs_flush) - {} - - msgpack_writer(bool integer_keys, bool needs_flush) - : msgpack_writer(epee::byte_stream{}, integer_keys, needs_flush) + msgpack_writer(epee::byte_stream&& sink, bool integer_keys, bool needs_flush) + : writer(), bytes_(std::move(sink)), expected_(1), integer_keys_(integer_keys), needs_flush_(needs_flush) {} //! \throw std::logic_error if tree was not completed void check_complete(); //! \throw std::logic_error if incomplete msgpack tree. \return msgpack bytes - epee::byte_slice take_msgpack(); + epee::byte_stream take_msgpack(); //! Flush bytes in local buffer to `do_flush(...)` void flush() @@ -157,22 +153,22 @@ namespace wire //! Buffers entire JSON message in memory struct msgpack_slice_writer final : msgpack_writer { - msgpack_slice_writer(epee::byte_stream&& initial, bool integer_keys = false) - : msgpack_writer(std::move(initial), integer_keys, false) + explicit msgpack_slice_writer(epee::byte_stream&& sink, bool integer_keys = false) + : msgpack_writer(std::move(sink), integer_keys, false) {} explicit msgpack_slice_writer(bool integer_keys = false) - : msgpack_writer(integer_keys, false) + : msgpack_slice_writer(epee::byte_stream{}, integer_keys) {} //! \throw std::logic_error if incomplete JSON tree \return JSON bytes - epee::byte_slice take_bytes() + epee::byte_stream take_sink() { return msgpack_writer::take_msgpack(); } }; - //! Periodically flushes JSON data to `std::ostream` + //! Periodically flushes MsgPack data to `std::ostream` class msgpack_stream_writer final : public msgpack_writer { std::ostream& dest; @@ -180,7 +176,7 @@ namespace wire virtual void do_flush(epee::span) override final; public: explicit msgpack_stream_writer(std::ostream& dest, bool integer_keys = false) - : msgpack_writer(integer_keys, true), dest(dest) + : msgpack_writer(epee::byte_stream{}, integer_keys, true), dest(dest) {} //! Flush remaining bytes to stream \throw std::logic_error if incomplete JSON tree @@ -190,44 +186,4 @@ namespace wire flush(); } }; - - template - epee::byte_slice msgpack::to_bytes(const T& source) - { - return wire_write::to_bytes(source); - } - - template - inline void array(msgpack_writer& dest, const T& source, F filter = F{}) - { - wire_write::array(dest, source, source.size(), std::move(filter)); - } - template - inline void write_bytes(msgpack_writer& dest, as_array_ source) - { - wire::array(dest, source.get_value(), std::move(source.filter)); - } - template - inline enable_if::value> write_bytes(msgpack_writer& dest, const T& source) - { - wire::array(dest, source); - } - - template - inline void dynamic_object(msgpack_writer& dest, const T& source, F key_filter = F{}, G value_filter = G{}) - { - // works with "lazily" computed ranges - wire_write::dynamic_object(dest, source, 0, std::move(key_filter), std::move(value_filter)); - } - template - inline void write_bytes(msgpack_writer& dest, as_object_ source) - { - wire::dynamic_object(dest, source.get_map(), std::move(source.key_filter), std::move(source.value_filter)); - } - - template - inline void object(msgpack_writer& dest, T... fields) - { - wire_write::object(dest, std::move(fields)...); - } } diff --git a/src/wire/read.h b/src/wire/read.h index 901ff57..f06aae6 100644 --- a/src/wire/read.h +++ b/src/wire/read.h @@ -67,6 +67,9 @@ namespace wire //! \return Maximum read depth for both objects and arrays before erroring static constexpr std::size_t max_read_depth() noexcept { return 100; } + //! \return Assume delimited arrays in generic interface (some optimizations disabled) + static constexpr std::true_type delimited_arrays() noexcept { return {}; } + reader() noexcept : depth_(0) {} @@ -137,32 +140,25 @@ namespace wire void end_object() noexcept { decrement_depth(); } }; - inline void read_bytes(reader& source, bool& dest) - { - dest = source.boolean(); - } + template + inline void read_bytes(R& source, bool& dest) + { dest = source.boolean(); } - inline void read_bytes(reader& source, double& dest) - { - dest = source.real(); - } + template + inline void read_bytes(R& source, double& dest) + { dest = source.real(); } - inline void read_bytes(reader& source, std::string& dest) - { - dest = source.string(); - } + template + inline void read_bytes(R& source, std::string& dest) + { dest = source.string(); } template inline void read_bytes(R& source, std::vector& dest) - { - dest = source.binary(); - } + { dest = source.binary(); } - template - inline enable_if::value> read_bytes(reader& source, T& dest) - { - source.binary(epee::as_mut_byte_span(dest)); - } + template + inline std::enable_if_t::value> read_bytes(R& source, T& dest) + { source.binary(epee::as_mut_byte_span(dest)); } namespace integer { @@ -205,17 +201,17 @@ namespace wire } //! read all current and future signed integer types - template - inline enable_if::value && std::is_integral::value> - read_bytes(reader& source, T& dest) + template + inline std::enable_if_t::value && std::is_integral::value> + read_bytes(R& source, T& dest) { dest = integer::cast_signed(source.integer()); } //! read all current and future unsigned integer types - template - inline enable_if::value && std::is_integral::value> - read_bytes(reader& source, T& dest) + template + inline std::enable_if_t::value && std::is_integral::value> + read_bytes(R& source, T& dest) { dest = integer::cast_unsigned(source.unsigned_integer()); } @@ -231,21 +227,27 @@ namespace wire_read [[noreturn]] void throw_exception(wire::error::schema code, const char* display, epee::span name_list); + template + inline void bytes(R& source, T&& dest) + { + read_bytes(source, dest); // ADL (searches every associated namespace) + } + //! \return `T` converted from `source` or error. - template - inline expect to(R& source) + template + inline std::error_code from_bytes(T&& source, U& dest) { try { - T dest{}; - read_bytes(source, dest); - source.check_complete(); - return dest; + R in{std::forward(source)}; + bytes(in, dest); + in.check_complete(); } catch (const wire::exception& e) { return e.code(); } + return {}; } template @@ -258,7 +260,7 @@ namespace wire_read std::size_t count = source.start_array(); dest.clear(); - dest.reserve(count); + wire::reserve(dest, count); bool more = count; while (more || !source.is_array_end(count)) @@ -272,37 +274,6 @@ namespace wire_read return source.end_array(); } - // `unpack_variant_field` identifies which of the variant types was selected. starts with index-0 - - template - inline void unpack_variant_field(std::size_t, R&, const T&) - {} - - template - inline void unpack_variant_field(const std::size_t index, R& source, T& variant, const wire::option& head, const wire::option&... tail) - { - if (index) - unpack_variant_field(index - 1, source, variant, tail...); - else - { - U dest{}; - read_bytes(source, dest); - variant = std::move(dest); - } - } - - // `unpack_field` expands `variant_field_`s or reads `field_`s directly - - template - inline void unpack_field(const std::size_t index, R& source, wire::variant_field_& dest) - { - unpack_variant_field(index, source, dest.get_value(), static_cast< const wire::option& >(dest)...); - } - - template - inline void reset_field(wire::variant_field_& dest) - {} - template inline void reset_field(wire::field_& dest) { @@ -320,34 +291,15 @@ namespace wire_read template inline void unpack_field(std::size_t, R& source, wire::field_& dest) { - read_bytes(source, dest.get_value()); + bytes(source, dest.get_value()); } template inline void unpack_field(std::size_t, R& source, wire::field_& dest) { - dest.get_value().emplace(); - read_bytes(source, *dest.get_value()); - } - - // `expand_field_map` writes a single `field_` name or all option names in a `variant_field_` to a table - - template - inline void expand_field_map(std::size_t, wire::reader::key_map (&)[N]) - {} - - template - inline void expand_field_map(std::size_t index, wire::reader::key_map (&map)[N], const T& head, const U&... tail) - { - map[index].name = head.name; - map[index].id = head.id(); - expand_field_map(index + 1, map, tail...); - } - - template - inline void expand_field_map(std::size_t index, wire::reader::key_map (&map)[N], const wire::variant_field_& field) - { - expand_field_map(index, map, static_cast< const wire::option & >(field)...); + if (!bool(dest.get_value())) + dest.get_value().emplace(); + bytes(source, *dest.get_value()); } //! Tracks read status of every object field instance. @@ -378,7 +330,8 @@ namespace wire_read std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N]) { our_index_ = index; - expand_field_map(index, map, field_); // expands possible inner options + map[index].id = field_.id(); + map[index].name = field_.name; return index + count(); } @@ -460,19 +413,14 @@ namespace wire_read namespace wire { - template - inline void array(reader& source, T& dest) - { - wire_read::array(source, dest); - } template - inline enable_if::value> read_bytes(R& source, T& dest) + inline std::enable_if_t::value> read_bytes(R& source, T& dest) { wire_read::array(source, dest); } - template - inline void object(reader& source, T... fields) + template + inline std::enable_if_t::value> object(R& source, T... fields) { wire_read::object(source, wire_read::tracker{std::move(fields)}...); } diff --git a/src/wire/traits.h b/src/wire/traits.h index 9f23af1..5f79c7a 100644 --- a/src/wire/traits.h +++ b/src/wire/traits.h @@ -30,20 +30,50 @@ #include #include +#define WIRE_DECLARE_BLOB(type) \ + template<> \ + struct is_blob \ + : std::true_type \ + {} + +#define WIRE_DECLARE_BLOB_NS(type) \ + namespace wire { WIRE_DECLARE_BLOB(type); } + namespace wire { - template - using enable_if = typename std::enable_if::type; + template + struct unwrap_reference + { + using type = std::remove_cv_t>; + }; + template + struct unwrap_reference> + : std::remove_cv + {}; + + template + using unwrap_reference_t = typename unwrap_reference::type; + + /*! Mark `T` as an array for writing, and reading when + `default_min_element_size::value != 0`. See `array_` in + `wrapper/array.h`. */ template struct is_array : std::false_type {}; + /*! Mark `T` as fixed binary data for reading+writing. Concept requirements + for reading: + * `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod` + and no padding). + Concept requirements for writing: + * `T` must be compatible with `epee::as_byte_span` (std::is_pod` and + no padding). */ template struct is_blob : std::false_type {}; -/*! Forces field to be optional when empty. Concept requirements for `T` when + /*! Forces field to be optional when empty. Concept requirements for `T` when `is_optional_on_empty::value == true`: * must have an `empty()` method that toggles whether the associated `wire::field_<...>` is omitted by the `wire::writer`. @@ -66,6 +96,15 @@ namespace wire return head + sum(tail...); } + //! If container has no `reserve(0)` function, this function is used + template + inline void reserve(const T&...) noexcept + {} + + //! Container has `reserve(std::size_t)` function, use it + template + inline auto reserve(T& container, const std::size_t count) -> decltype(container.reserve(count)) + { return container.reserve(count); } //! If `T` has no `empty()` function, this function is used template diff --git a/src/wire/wrapper/CMakeLists.txt b/src/wire/wrapper/CMakeLists.txt new file mode 100644 index 0000000..b1b6ea5 --- /dev/null +++ b/src/wire/wrapper/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2020, The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(monero-lws-wire_sources variant.cpp) +set(monero-lws-wire_headers variant.h) + +add_library(monero-lws-wire-wrapper ${monero-lws-wire_sources} ${monero-lws-wire_headers}) +target_include_directories(monero-lws-wire-wrapper PUBLIC "${LMDB_INCLUDE}") +target_link_libraries(monero-lws-wire-wrapper PRIVATE monero::libraries) + diff --git a/src/wire/wrapper/array.h b/src/wire/wrapper/array.h new file mode 100644 index 0000000..99513fb --- /dev/null +++ b/src/wire/wrapper/array.h @@ -0,0 +1,158 @@ +// Copyright (c) 2021, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include + +//#include "wire/field.h" +#include "wire/traits.h" + +/*! An array field with read constraint. See `array_` for more info. All (empty) + arrays were "optional" (omitted) historically in epee, so this matches prior + behavior. */ +#define WIRE_FIELD_ARRAY(name, read_constraint) \ + ::wire::optional_field( #name , ::wire::array< read_constraint >(std::ref( self . name ))) + +namespace wire +{ + /*! A wrapper that ensures `T` is written as an array, with `C` constraints + when reading (`max_element_count` or `min_element_size`). `C` can be `void` + if write-only. + This wrapper meets the requirements for an optional field; `wire::field` + and `wire::optional_field` determine whether an empty array must be + encoded on the wire. Historically, empty arrays were always omitted on + the wire (a defacto optional field). + The `is_array` trait can also be used, but is default treated as an optional + field. The trait `is_optional_on_empty` traits can be specialized to disable + the optional on empty behavior. See `wire/traits.h` for more ifnormation + on the `is_optional_on_empty` trait. + `container_type` is `T` with optional `std::reference_wrapper` removed. + `container_type` concept requirements: + * `typedef` `value_type` that specifies inner type. + * must have `size()` method that returns number of elements. + Additional concept requirements for `container_type` when reading: + * must have `clear()` method that removes all elements (`size() == 0`). + * must have either: (1) `end()` and `emplace_hint(iterator, value_type&&)` + or (2) `emplace_back()` and `back()`: + * `end()` method that returns one-past the last element. + * `emplace_hint(iterator, value_type&&)` method that move constructs a new + element. + * `emplace_back()` method that default initializes new element + * `back()` method that retrieves last element by reference. + Additional concept requirements for `container_type` when writing: + * must work with foreach loop (`std::begin` and `std::end`). + * must work with `boost::size` (from the `boost::range` library). */ + template + struct array_ + { + using constraint = C; + using container_type = unwrap_reference_t; + using value_type = typename container_type::value_type; + + // See nested `array_` overload below + using inner_array = std::reference_wrapper; + using inner_array_const = std::reference_wrapper; + + T container; + + constexpr const container_type& get_container() const noexcept { return container; } + container_type& get_container() noexcept { return container; } + + //! Read directly into the non-nested array + container_type& get_read_object() noexcept { return get_container(); } + + + // concept requirements for optional fields + + explicit operator bool() const noexcept { return !get_container().empty(); } + array_& emplace() noexcept { return *this; } + + array_& operator*() noexcept { return *this; } + const array_& operator*() const noexcept { return *this; } + + void reset() { get_container().clear(); } + }; + + //! Nested array case + template + struct array_, D> + { + // compute `container_type` and `value_type` recursively + using constraint = D; + using container_type = typename array_::container_type; + using value_type = typename container_type::value_type; + + // Re-compute `array_` for inner values + using inner_array = array_::inner_array, C>; + using inner_array_const = array_::inner_array_const, C>; + + array_ nested; + + const container_type& get_container() const noexcept { return nested.get_container(); } + container_type& get_container() noexcept { return nested.get_container(); } + + //! Read through this proxy to track nested array + array_& get_read_object() noexcept { return *this; } + + + // concept requirements for optional fields + + explicit operator bool() const noexcept { return !empty(); } + array_& emplace() noexcept { return *this; } + + array_& operator*() noexcept { return *this; } + const array_& operator*() const noexcept { return *this; } + + void reset() { clear(); } + + + /* For reading nested arrays. writing nested arrays is handled in + `wrappers_impl.h` with range transform. */ + + void clear() { get_container().clear(); } + bool empty() const noexcept { return get_container().empty(); } + std::size_t size() const noexcept { return get_container().size(); } + + void emplace_back() { get_container().emplace_back(); } + + //! \return A proxy object for tracking inner-array constraints + inner_array back() noexcept { return {std::ref(get_container().back())}; } + }; + + //! Treat `value` as an array when reading/writing, and constrain reading with `C`. + template + inline constexpr array_ array(T value) + { + return {std::move(value)}; + } + + /* Do not register with `is_optional_on_empty` trait, this allows selection + on whether an array is mandatory on wire. */ + +} // wire diff --git a/src/wire/wrapper/variant.cpp b/src/wire/wrapper/variant.cpp new file mode 100644 index 0000000..74aa263 --- /dev/null +++ b/src/wire/wrapper/variant.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2022, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "wire/wrapper/variant.h" + +#include +#include "wire/error.h" + +namespace wire +{ + [[noreturn]] void throw_variant_exception(wire::error::schema type, const char* variant_name) + { + WIRE_DLOG_THROW(type, "error with variant type: " << boost::core::demangle(variant_name)); + } +} diff --git a/src/wire/wrapper/variant.h b/src/wire/wrapper/variant.h new file mode 100644 index 0000000..c61970d --- /dev/null +++ b/src/wire/wrapper/variant.h @@ -0,0 +1,207 @@ +// Copyright (c) 2022, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include + +#include "wire/error.h" +#include "wire/fwd.h" +#include "wire/field.h" +#include "wire/read.h" +#include "wire/write.h" + +#define WIRE_OPTION(name, type, cpp_name) \ + wire::optional_field(name, wire::option(std::ref(cpp_name))) + +namespace wire +{ + [[noreturn]] void throw_variant_exception(error::schema type, const char* variant_name); + + /*! Wrapper for any C++ variant type that tracks if a `read_bytes` call has + completed on wrapped `value`. This wrapper is not needed if the variant is + being used for writes only - see `wire::option_` below for more information. + + `variant_type` is `T` with optional `std::reference_wrapper` removed. See + `option_` for concept requirements of `variant_type`. + + Example usage: + ``` + template + void type_map(F& format, T& self) + { + auto variant = wire::variant(std::ref(self.field3)); + wire::object(format, + ... + WIRE_OPTION("type1", type1, variant), + WIRE_OPTION("type2", type2, variant) + ); + } + ``` */ + template + struct variant_ + { + using variant_type = unwrap_reference_t; + + //! \throw wire::exception with `type` and mangled C++ name of `variant_type`. + [[noreturn]] static void throw_exception(const error::schema type) + { throw_variant_exception(type, typeid(variant_type).name()); } + + constexpr variant_(T&& value) + : value(std::move(value)), read(false) + {} + + T value; + bool read; + + constexpr const variant_type& get_variant() const noexcept { return value; } + variant_type& get_variant() noexcept { return value; } + + //! Makes `variant_` compatible with `emplace()` in `option_`. + template + variant_& operator=(U&& rhs) + { + get_variant() = std::forward(rhs); + return *this; + } + }; + + template + inline constexpr variant_ variant(T value) + { return {std::move(value)}; } + + namespace adapt + { + template + inline void throw_if_not_read(const T&) + { throw_variant_exception(error::schema::missing_key, typeid(T).name()); } + + template + inline void throw_if_not_read(const variant_& value) + { + if (!value.read) + value.throw_exception(error::schema::missing_key); + } + + + // other variant overloads can be added here as needed + + template + inline const U* get_if(const boost::variant& value) + { return boost::get(std::addressof(value)); } + + template + inline const U* get_if(const variant_& value) + { return adapt::get_if(value.get_variant()); } + } + + /*! Wrapper that makes a variant compatible with `wire::optional_field`. + Currently `wire::variant_` and `boost::variant` are valid variant types + for writing, and only `wire::variant_` is valid for reading. + + `variant_type` is `T` with optional `std::reference_wrapper` removed. + `variant_type` concept requirements: + * must have two overloads for `get` function in `adapt` namespace - one + `const` and one non-`const` that returns `const U&` and `U&` respectively + iff `variant_type` is storing type `U`. Otherwise, the function should + throw an exception. + * must have overload for `get_if` function in `adapt` namespace that + returns `const U*` when `variant_type` is storing type `U`. Otherwise, the + function should return `nullptr`. + * must have a member function `operator=(U&&)` that changes the stored type + to `U` (`get` and `get_if` will return `U` after `operator=` + completion). + + The `wire::variant(std::ref(self.field3))` step in the example above can be + omitted if only writing is needed. The `boost::variant` value should be + given directly to `wire::option(...)` or `WIRE_OPTION` macro - only one + type is active so `wire::optional_field` will omit all other types/fields. */ + template + struct option_ + { + using variant_type = unwrap_reference_t; + using option_type = U; + + T value; + + constexpr const variant_type& get_variant() const noexcept { return value; } + variant_type& get_variant() noexcept { return value; } + + //! \return `true` iff `U` is active type in variant. + bool is_active() const { return adapt::get_if(get_variant()) != nullptr; } + + + // concept requirements for optional fields + + explicit operator bool() const { return is_active(); } + void emplace() { get_variant() = U{}; } + + const option_& operator*() const { return *this; } + option_& operator*() { return *this; } + + //! \throw wire::exception iff no variant type was read. + void reset() { adapt::throw_if_not_read(get_variant()); } + }; + + template + inline constexpr option_ option(T value) + { return {std::move(value)}; } + + namespace adapt + { + // other variant overloads can be added here as needed + + template + inline U& get(boost::variant& value) + { return boost::get(value); } + + template + inline const U& get(const boost::variant& value) + { return boost::get(value); } + + template + inline const U& get(const variant_& value) + { return adapt::get(value.get_variant()); } + } + + //! \throw wire::exception if `dest.get_variant()` has already been used in `read_bytes`. + template + inline void read_bytes(R& source, option_>, U> dest) + { + if (dest.get_variant().read) + dest.get_variant().throw_exception(error::schema::invalid_key); + wire_read::bytes(source, adapt::get(dest.get_variant().get_variant())); + dest.get_variant().read = true; + } + + template + inline void write_bytes(W& dest, const option_& source) + { wire_write::bytes(dest, adapt::get(source.get_variant())); } +} diff --git a/src/wire/wrappers_impl.h b/src/wire/wrappers_impl.h new file mode 100644 index 0000000..7166358 --- /dev/null +++ b/src/wire/wrappers_impl.h @@ -0,0 +1,62 @@ +// Copyright (c) 2022, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include "wire/error.h" +#include "wire/read.h" +#include "wire/write.h" +#include "wire/wrapper/array.h" + +namespace wire +{ + // + // free functions for `array_` wrapper + // + + template + inline void read_bytes(R& source, array_ wrapper) + { + // see constraints directly above `array_` definition + static_assert(std::is_same::value, "array_ must have a read constraint for memory purposes"); + wire_read::array(source, wrapper.get_read_object()); + } + + template + inline void write_bytes(W& dest, const array_& wrapper) + { + wire_write::array(dest, wrapper.get_container()); + } + template + inline void write_bytes(W& dest, const array_, D>& wrapper) + { + using inner_type = typename array_, D>::inner_array_const; + const auto wrap = [](const auto& val) -> inner_type { return {std::ref(val)}; }; + wire_write::array(dest, boost::adaptors::transform(wrapper.get_container(), wrap)); + } +} // wire diff --git a/src/wire/write.h b/src/wire/write.h index f92c210..bc3c291 100644 --- a/src/wire/write.h +++ b/src/wire/write.h @@ -29,11 +29,16 @@ #include #include +#include +#include #include #include +#include #include "byte_slice.h" // monero/contrib/epee/include +#include "byte_stream.h"// monero/contrib/epee/include #include "span.h" // monero/contrib/epee/include +#include "wire/error.h" #include "wire/field.h" #include "wire/filters.h" #include "wire/traits.h" @@ -47,6 +52,9 @@ namespace wire virtual ~writer() noexcept; + //! By default, insist on retrieving array size before writing array + static constexpr std::true_type need_array_size() noexcept { return{}; } + virtual void boolean(bool) = 0; virtual void integer(int) = 0; @@ -78,59 +86,62 @@ namespace wire writer& operator=(writer&&) = default; }; - // leave in header, compiler can de-virtualize when final type is given + template + inline void write_bytes(W& dest, const bool source) + { dest.boolean(source); } - inline void write_bytes(writer& dest, const bool source) - { - dest.boolean(source); - } + template + inline void write_arithmetic(W& dest, const int source) + { dest.integer(source); } - inline void write_bytes(writer& dest, const int source) - { - dest.integer(source); - } - inline void write_bytes(writer& dest, const long source) - { - dest.integer(std::intmax_t(source)); - } - inline void write_bytes(writer& dest, const long long source) - { - dest.integer(std::intmax_t(source)); - } + template + inline void write_arithmetic(W& dest, const long source) + { dest.integer(std::intmax_t(source)); } - inline void write_bytes(writer& dest, const unsigned source) - { - dest.unsigned_integer(source); - } - inline void write_bytes(writer& dest, const unsigned long source) - { - dest.unsigned_integer(std::uintmax_t(source)); - } - inline void write_bytes(writer& dest, const unsigned long long source) - { - dest.unsigned_integer(std::uintmax_t(source)); - } + template + inline void write_arithmetic(W& dest, const long long source) + { dest.integer(std::intmax_t(source)); } - inline void write_bytes(writer& dest, const double source) - { - dest.real(source); - } + template + inline void write_arithmetic(W& dest, const unsigned source) + { dest.unsigned_integer(source); } - inline void write_bytes(writer& dest, const boost::string_ref source) - { - dest.string(source); - } + template + inline void write_arithmetic(W& dest, const unsigned long source) + { dest.unsigned_integer(std::uintmax_t(source)); } - template - inline enable_if::value> write_bytes(writer& dest, const T& source) - { - dest.binary(epee::as_byte_span(source)); - } + template + inline void write_arithmetic(W& dest, const unsigned long long source) + { dest.unsigned_integer(std::uintmax_t(source)); } - inline void write_bytes(writer& dest, const epee::span source) - { - dest.binary(source); - } + template + inline std::enable_if_t::value> write_bytes(W& dest, const T source) + { write_arithmetic(dest, source); } + + template + inline void write_bytes(W& dest, const double source) + { dest.real(source); } + + template + inline void write_bytes(W& dest, const boost::string_ref source) + { dest.string(source); } + + template + inline std::enable_if_t::value> write_bytes(W& dest, const T& source) + { dest.binary(epee::as_byte_span(source)); } + + template + inline void write_bytes(W& dest, const epee::span source) + { dest.binary(source); } + + template + inline void write_bytes(W& dest, const epee::byte_slice& source) + { write_bytes(dest, epee::to_span(source)); } + + //! Use `write_bytes(...)` method if available for `T`. + template + inline auto write_bytes(W& dest, const T& source) -> decltype(source.write_bytes(dest)) + { return source.write_bytes(dest); } } namespace wire_write @@ -142,39 +153,80 @@ namespace wire_write declared after these functions. */ template - inline epee::byte_slice to_bytes(W&& dest, const T& value) + inline void bytes(W& dest, const T& source) { - write_bytes(dest, value); - return dest.take_bytes(); + write_bytes(dest, source); // ADL (searches every associated namespace) + } + + template + inline std::error_code to_bytes(T& dest, const U& source) + { + try + { + W out{std::move(dest)}; + bytes(out, source); + dest = out.take_sink(); + } + catch (const wire::exception& e) + { + dest.clear(); + return e.code(); + } + catch (...) + { + dest.clear(); + throw; + } + return {}; } template - inline epee::byte_slice to_bytes(const T& value) + inline std::error_code to_bytes(epee::byte_slice& dest, const T& source) { - return wire_write::to_bytes(W{}, value); + epee::byte_stream sink{}; + const std::error_code error = wire_write::to_bytes(sink, source); + if (error) + { + dest = nullptr; + return error; + } + dest = epee::byte_slice{std::move(sink)}; + return {}; } - template - inline void array(W& dest, const T& source, const std::size_t count, F filter = F{}) + template + inline std::size_t array_size_(std::true_type, const T& source) + { return boost::size(source); } + + template + inline constexpr std::size_t array_size_(std::false_type, const T&) noexcept + { return 0; } + + template + inline constexpr std::size_t array_size(const W& dest, const T& source) noexcept + { return array_size_(dest.need_array_size(), source); } + + template + inline void array(W& dest, const T& source) { using value_type = typename T::value_type; static_assert(!std::is_same::value, "write array of chars as binary"); static_assert(!std::is_same::value, "write array of unsigned chars as binary"); - dest.start_array(count); + dest.start_array(array_size(dest, source)); for (const auto& elem : source) - write_bytes(dest, filter(elem)); + bytes(dest, elem); dest.end_array(); } template inline bool field(W& dest, const wire::field_ elem) { - // Arrays always optional, see `wire/field.h` + // Arrays always optional, see `wire::field.h` if (wire::available(elem)) { dest.key(I, elem.name); - write_bytes(dest, elem.get_value()); + bytes(dest, elem.get_value()); } return true; } @@ -185,7 +237,7 @@ namespace wire_write if (wire::available(elem)) { dest.key(I, elem.name); - write_bytes(dest, *elem.get_value()); + bytes(dest, *elem.get_value()); } return true; } @@ -199,13 +251,13 @@ namespace wire_write } template - inline void dynamic_object(W& dest, const T& values, const std::size_t count, F key_filter, G value_filter) + inline void dynamic_object(W& dest, const T& values, F key_filter, G value_filter) { - dest.start_object(count); + dest.start_object(array_size(dest, values)); for (const auto& elem : values) { dest.key(key_filter(elem.first)); - write_bytes(dest, value_filter(elem.second)); + bytes(dest, value_filter(elem.second)); } dest.end_object(); } @@ -213,35 +265,31 @@ namespace wire_write namespace wire { - template - inline void array(writer& dest, const T& source, F filter = F{}) + template + inline void write_bytes(W& dest, const as_array_ source) { - wire_write::array(dest, source, source.size(), std::move(filter)); + wire_write::array(dest, boost::adaptors::transform(source.get_value(), source.filter)); } - template - inline void write_bytes(writer& dest, as_array_ source) + template + inline std::enable_if_t::value> write_bytes(W& dest, const T& source) { - wire::array(dest, source.get_value(), std::move(source.filter)); - } - template - inline enable_if::value> write_bytes(writer& dest, const T& source) - { - wire::array(dest, source); + wire_write::array(dest, source); } - template - inline void dynamic_object(writer& dest, const T& source, F key_filter = F{}, G value_filter = G{}) + template + inline std::enable_if_t::value> + dynamic_object(W& dest, const T& source, F key_filter = F{}, G value_filter = G{}) { - wire_write::dynamic_object(dest, source, source.size(), std::move(key_filter), std::move(value_filter)); + wire_write::dynamic_object(dest, source, std::move(key_filter), std::move(value_filter)); } - template - inline void write_bytes(writer& dest, as_object_ source) + template + inline void write_bytes(W& dest, as_object_ source) { wire::dynamic_object(dest, source.get_map(), std::move(source.key_filter), std::move(source.value_filter)); } - template - inline void object(writer& dest, T... fields) + template + inline std::enable_if_t::value> object(W& dest, T... fields) { wire_write::object(dest, std::move(fields)...); } diff --git a/tests/unit/rpc/admin.test.cpp b/tests/unit/rpc/admin.test.cpp index a9a5dfe..ed5744b 100644 --- a/tests/unit/rpc/admin.test.cpp +++ b/tests/unit/rpc/admin.test.cpp @@ -46,12 +46,13 @@ namespace expect call_endpoint(lws::db::storage disk, std::string json) { using request_type = typename T::request; - expect req = wire::json::from_bytes(std::move(json)); - if (!req) - return req.error(); + request_type req{}; + const std::error_code error = wire::json::from_bytes(std::move(json), req); + if (error) + return error; wire::json_slice_writer out{}; - MONERO_CHECK(T{}(out, std::move(disk), std::move(*req))); - return out.take_bytes(); + MONERO_CHECK(T{}(out, std::move(disk), std::move(req))); + return epee::byte_slice{out.take_sink()}; } } @@ -138,7 +139,7 @@ LWS_CASE("rpc::admin") { wire::json_slice_writer out{}; EXPECT(lws::rpc::webhook_list(out, db.clone())); - expect bytes = out.take_bytes(); + expect bytes = epee::byte_slice{out.take_sink()}; EXPECT(!bytes.has_error()); { diff --git a/tests/unit/wire/json/read.write.test.cpp b/tests/unit/wire/json/read.write.test.cpp index 47aab43..f67f035 100644 --- a/tests/unit/wire/json/read.write.test.cpp +++ b/tests/unit/wire/json/read.write.test.cpp @@ -47,16 +47,15 @@ namespace { SETUP("Basic values with " + boost::core::demangle(typeid(T).name()) + " integers") { - const auto result = - wire::json::from_bytes>(std::string{basic_json}); - EXPECT(result); - EXPECT(result->utf8 == basic_string); + basic_object result{}; + EXPECT(!wire::json::from_bytes(std::string{basic_json}, result)); + EXPECT(result.utf8 == basic_string); { const std::vector expected{0, 127}; - EXPECT(result->vec == expected); + EXPECT(result.vec == expected); } - EXPECT(result->data == lws_test::blob_test1); - EXPECT(result->choice); + EXPECT(result.data == lws_test::blob_test1); + EXPECT(result.choice); } } @@ -66,7 +65,8 @@ namespace SETUP("Basic values with " + boost::core::demangle(typeid(T).name()) + " integers") { const basic_object val{basic_string, std::vector{0, 127}, lws_test::blob_test1, true}; - const auto result = wire::json::to_bytes(val); + epee::byte_slice result{}; + EXPECT(!wire::json::to_bytes(result, val)); EXPECT(boost::range::equal(result, std::string{basic_json})); } } @@ -91,9 +91,11 @@ LWS_CASE("wire::json_reader") i64_limit::max() <= std::numeric_limits::max(), "expected int64_t::max <= uintmax_t::max" ); + std::uint64_t one = 0; + std::int64_t two = 0; std::string big_number = std::to_string(std::uintmax_t(i64_limit::max()) + 1); - EXPECT(wire::json::from_bytes(negative_number) == wire::error::schema::larger_integer); - EXPECT(wire::json::from_bytes(std::move(big_number)) == wire::error::schema::smaller_integer); + EXPECT(wire::json::from_bytes(negative_number, one) == wire::error::schema::larger_integer); + EXPECT(wire::json::from_bytes(std::move(big_number), two) == wire::error::schema::smaller_integer); } LWS_CASE("wire::json_writer") diff --git a/tests/unit/wire/msgpack/read.write.test.cpp b/tests/unit/wire/msgpack/read.write.test.cpp index 50bdb2b..e91eebc 100644 --- a/tests/unit/wire/msgpack/read.write.test.cpp +++ b/tests/unit/wire/msgpack/read.write.test.cpp @@ -86,16 +86,17 @@ namespace { SETUP("Basic (string keys) with " + boost::core::demangle(typeid(T).name()) + " integers") { - const auto result = - wire::msgpack::from_bytes>(epee::byte_slice{{basic_msgpack}}); - EXPECT(result); - EXPECT(result->utf8 == basic_string); + basic_object result{}; + const std::error_code error = + wire::msgpack::from_bytes(epee::byte_slice{{basic_msgpack}}, result); + EXPECT(!error); + EXPECT(result.utf8 == basic_string); { const std::vector expected{0, 127}; - EXPECT(result->vec == expected); + EXPECT(result.vec == expected); } - EXPECT(result->data == lws_test::blob_test1); - EXPECT(result->choice); + EXPECT(result.data == lws_test::blob_test1); + EXPECT(result.choice); } } @@ -104,16 +105,17 @@ namespace { SETUP("Advanced (integer keys) with " + boost::core::demangle(typeid(T).name()) + " integers") { - const auto result = - wire::msgpack::from_bytes>(epee::byte_slice{{advanced_msgpack}}); - EXPECT(result); - EXPECT(result->utf8 == basic_string); + basic_object result{}; + const std::error_code error = + wire::msgpack::from_bytes(epee::byte_slice{{advanced_msgpack}}, result); + EXPECT(!error); + EXPECT(result.utf8 == basic_string); { const std::vector expected{0, 127}; - EXPECT(result->vec == expected); + EXPECT(result.vec == expected); } - EXPECT(result->data == lws_test::blob_test1); - EXPECT(result->choice); + EXPECT(result.data == lws_test::blob_test1); + EXPECT(result.choice); } } @@ -123,7 +125,9 @@ namespace SETUP("Basic (string keys) with " + boost::core::demangle(typeid(T).name()) + " integers") { const basic_object val{basic_string, std::vector{0, 127}, lws_test::blob_test1, true}; - const auto result = wire::msgpack::to_bytes(val); + epee::byte_slice result{}; + const std::error_code error = wire::msgpack::to_bytes(result, val); + EXPECT(!error); EXPECT(boost::range::equal(result, epee::byte_slice{{basic_msgpack}})); } } @@ -134,7 +138,12 @@ namespace SETUP("Advanced (integer keys) with " + boost::core::demangle(typeid(T).name()) + " integers") { const basic_object val{basic_string, std::vector{0, 127}, lws_test::blob_test1, true}; - const auto result = wire_write::to_bytes(wire::msgpack_slice_writer{true}, val); + epee::byte_slice result{}; + { + wire::msgpack_slice_writer out{true}; + wire_write::bytes(out, val); + result = epee::byte_slice{out.take_sink()}; + } EXPECT(boost::range::equal(result, epee::byte_slice{{advanced_msgpack}})); } } diff --git a/tests/unit/wire/read.write.test.cpp b/tests/unit/wire/read.write.test.cpp index d194133..1607413 100644 --- a/tests/unit/wire/read.write.test.cpp +++ b/tests/unit/wire/read.write.test.cpp @@ -146,23 +146,23 @@ namespace verify_initial(lest_env, base); { - const expect bytes = T::to_bytes(base); - EXPECT(bytes); + epee::byte_slice bytes{}; + EXPECT(!T::to_bytes(bytes, base)); - const expect derived = T::template from_bytes(U{std::string{bytes->begin(), bytes->end()}}); - EXPECT(derived); - verify_initial(lest_env, *derived); + complex derived{}; + EXPECT(!T::template from_bytes(U{std::string{bytes.begin(), bytes.end()}}, derived)); + verify_initial(lest_env, derived); } fill(base); { - const expect bytes = T::to_bytes(base); - EXPECT(bytes); + epee::byte_slice bytes{}; + EXPECT(!T::to_bytes(bytes, base)); - const expect derived = T::template from_bytes(U{std::string{bytes->begin(), bytes->end()}}); - EXPECT(derived); - verify_filled(lest_env, *derived); + complex derived{}; + EXPECT(!T::template from_bytes(U{std::string{bytes.begin(), bytes.end()}}, derived)); + verify_filled(lest_env, derived); } } } @@ -184,12 +184,15 @@ namespace template expect round_trip(lest::env& lest_env, std::int64_t value) { - expect out = small{0}; + small out{0}; SETUP("Testing round-trip with " + std::to_string(value)) { - const expect bytes = T::template to_bytes(big{value}); - EXPECT(bytes); - out = T::template from_bytes(U{std::string{bytes->begin(), bytes->end()}}); + epee::byte_slice bytes{}; + EXPECT(!T::template to_bytes(bytes, big{value})); + const std::error_code error = + T::template from_bytes(U{std::string{bytes.begin(), bytes.end()}}, out); + if (error) + return error; } return out; } @@ -236,12 +239,12 @@ namespace verify_initial(lest_env, base); fill(base); - const expect bytes = T::to_bytes(base); - EXPECT(bytes); + epee::byte_slice bytes{}; + EXPECT(!T::to_bytes(bytes, base)); - const expect derived = T::template from_bytes(U{std::string{bytes->begin(), bytes->end()}}); - EXPECT(derived); - EXPECT(derived->choice); + simple derived{}; + EXPECT(!T::template from_bytes(U{std::string{bytes.begin(), bytes.end()}}, derived)); + EXPECT(derived.choice); } }