From 510010cf0cb306c6ba79e37c701da8a78f4ab678 Mon Sep 17 00:00:00 2001 From: Lee *!* Clagett Date: Fri, 11 Aug 2023 15:23:54 -0400 Subject: [PATCH] Read interface for new serialization system --- .../include/serialization/wire/adapted/asio.h | 36 ++ .../wire/adapted/static_vector.h | 91 +++ .../wire/adapted/unordered_map.h | 40 ++ .../include/serialization/wire/basic_value.h | 64 ++ .../epee/include/serialization/wire/field.h | 19 +- .../epee/include/serialization/wire/read.h | 547 ++++++++++++++++++ .../serialization/wire/wrapper/array.h | 161 ++++++ .../serialization/wire/wrapper/array_blob.h | 95 +++ .../include/serialization/wire/wrapper/blob.h | 87 +++ .../serialization/wire/wrapper/variant.h | 205 +++++++ contrib/epee/src/wire/asio.cpp | 48 ++ contrib/epee/src/wire/basic_value.cpp | 55 ++ contrib/epee/src/wire/read.cpp | 88 +++ 13 files changed, 1529 insertions(+), 7 deletions(-) create mode 100644 contrib/epee/include/serialization/wire/adapted/asio.h create mode 100644 contrib/epee/include/serialization/wire/adapted/static_vector.h create mode 100644 contrib/epee/include/serialization/wire/adapted/unordered_map.h create mode 100644 contrib/epee/include/serialization/wire/basic_value.h create mode 100644 contrib/epee/include/serialization/wire/read.h create mode 100644 contrib/epee/include/serialization/wire/wrapper/array.h create mode 100644 contrib/epee/include/serialization/wire/wrapper/array_blob.h create mode 100644 contrib/epee/include/serialization/wire/wrapper/blob.h create mode 100644 contrib/epee/include/serialization/wire/wrapper/variant.h create mode 100644 contrib/epee/src/wire/asio.cpp create mode 100644 contrib/epee/src/wire/basic_value.cpp create mode 100644 contrib/epee/src/wire/read.cpp diff --git a/contrib/epee/include/serialization/wire/adapted/asio.h b/contrib/epee/include/serialization/wire/adapted/asio.h new file mode 100644 index 000000000..2d74fbb28 --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/asio.h @@ -0,0 +1,36 @@ +// 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 "serialization/wire/fwd.h" + +namespace wire +{ + WIRE_DECLARE_OBJECT(boost::asio::ip::address_v6); +} diff --git a/contrib/epee/include/serialization/wire/adapted/static_vector.h b/contrib/epee/include/serialization/wire/adapted/static_vector.h new file mode 100644 index 000000000..2f1096ee6 --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/static_vector.h @@ -0,0 +1,91 @@ +// 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 "serialization/wire/read.h" + +namespace wire +{ + // enable writing of static vector arrays + template + struct is_array> + : std::true_type + {}; + + // `static_vector`s of `char` and `uint8_t` are not arrays + template + struct is_array> + : std::false_type + {}; + template + struct is_array> + : std::false_type + {}; + + // `static_vector` can be used without specialized macro for every type, it provides max element count + template + inline void read_bytes(R& source, boost::container::static_vector& dest) + { + wire_read::array(source, dest, min_element_size<0>{}, max_element_count{}); + } + + /* `static_vector` never allocates, so it is useful for reading small variable + sized strings or binary data with a known fixed max. `char` and + `std::uint8_t` are not valid types for arrays in this design anyway + (because its clearly less efficient in every encoding scheme). */ + + template + inline void read_bytes(R& source, boost::container::static_vector& dest) + { + dest.resize(N); + dest.resize(source.string(epee::to_mut_span(dest), /* exact= */ false)); + } + + template + inline void write_bytes(W& dest, const boost::container::static_vector& source) + { + dest.string(boost::string_ref{source.data(), source.size()}); + } + + template + inline void read_bytes(R& source, boost::container::static_vector& dest) + { + dest.resize(N); + dest.resize(source.binary(epee::to_mut_span(dest), /* exact= */ false)); + } + + template + inline void write_bytes(W& dest, const boost::container::static_vector& source) + { + dest.binary(epee::to_span(source)); + } +} diff --git a/contrib/epee/include/serialization/wire/adapted/unordered_map.h b/contrib/epee/include/serialization/wire/adapted/unordered_map.h new file mode 100644 index 000000000..fa8b4703b --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/unordered_map.h @@ -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. + +#pragma once + +#include +#include "serialization/wire/write.h" + +namespace wire +{ + // no `read_bytes`, requires implementation with "array read constraints" + + template + inline void write_bytes(W& dest, const std::unordered_map& source) + { wire_write::dynamic_object(dest, source); } +} diff --git a/contrib/epee/include/serialization/wire/basic_value.h b/contrib/epee/include/serialization/wire/basic_value.h new file mode 100644 index 000000000..9d53e0cde --- /dev/null +++ b/contrib/epee/include/serialization/wire/basic_value.h @@ -0,0 +1,64 @@ +// 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 "serialization/wire/fwd.h" + +namespace wire +{ + /*! Can hold any non-recursive value. Implements optional field concept + requirements. If used in a `optional_field`, the `nullptr` type/value + determines whether the field name is omitted or present in an object. If used + in a `field` (required), the field name is always present in the object, and + the value could be `null`/`nil`. */ + struct basic_value + { + using variant_type = + boost::variant; + + variant_type value; + + // concept requirements for optional fields + + explicit operator bool() const noexcept { return value != variant_type{nullptr}; } + basic_value& emplace() noexcept { return *this; } + + basic_value& operator*() noexcept { return *this; } + const basic_value& operator*() const noexcept { return *this; } + + void reset(); + }; + + void read_bytes(reader& source, basic_value& dest); + void write_bytes(writer& dest, const basic_value& source); +} // wire diff --git a/contrib/epee/include/serialization/wire/field.h b/contrib/epee/include/serialization/wire/field.h index 402fa9ad4..e238719fc 100644 --- a/contrib/epee/include/serialization/wire/field.h +++ b/contrib/epee/include/serialization/wire/field.h @@ -32,9 +32,13 @@ #include "serialization/wire/traits.h" +//! A required field with the same key name and C/C++ name +#define WIRE_FIELD_ID(id, name) \ + ::wire::field< id >( #name , std::ref( self . name )) + //! A required field has the same key name and C/C++ name -#define WIRE_FIELD(name) \ - ::wire::field( #name , std::ref( self . name )) +#define WIRE_FIELD(name) \ + WIRE_FIELD_ID(0, name) //! A required field has the same key name and C/C++ name AND is cheap to copy (faster output). #define WIRE_FIELD_COPY(name) \ @@ -92,7 +96,7 @@ namespace wire Basically each input/output format needs a unique type so that the compiler knows how to "dispatch" the read/write calls. */ - template + template struct field_ { using value_type = unwrap_reference_t; @@ -103,6 +107,7 @@ namespace wire static constexpr bool is_required() noexcept { return Required && !optional_on_empty(); } static constexpr std::size_t count() noexcept { return 1; } + static constexpr unsigned id() noexcept { return I; } const char* name; T value; @@ -112,15 +117,15 @@ namespace wire }; //! Links `name` to `value`. Use `std::ref` if de-serializing. - template - constexpr inline field_ field(const char* name, T value) + template + constexpr inline field_ field(const char* name, T value) { return {name, std::move(value)}; } //! Links `name` to optional `value`. Use `std::ref` if de-serializing. - template - constexpr inline field_ optional_field(const char* name, T value) + template + constexpr inline field_ optional_field(const char* name, T value) { return {name, std::move(value)}; } diff --git a/contrib/epee/include/serialization/wire/read.h b/contrib/epee/include/serialization/wire/read.h new file mode 100644 index 000000000..85baec9e9 --- /dev/null +++ b/contrib/epee/include/serialization/wire/read.h @@ -0,0 +1,547 @@ +// Copyright (c) 2023, 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 "byte_slice.h" +#include "serialization/wire/epee/fwd.h" +#include "serialization/wire/error.h" +#include "serialization/wire/field.h" +#include "serialization/wire/fwd.h" +#include "serialization/wire/traits.h" +#include "span.h" + +/* + Custom types (e.g. `type` in namespace `ns`) can define an input function by: + * `namespace wire { template<> struct is_blob : std::true_type {}; }` + * `namespace wire { void read_bytes(writer&, ns::type&); }` + * `namespace ns { void read_bytes(wire::writer&, type&); }` + + See `traits.h` for `is_blob` requirements. `read_bytes` function can also + specify derived type for faster output (i.e. + `namespace ns { void read_bytes(wire::epee_reader&, type&); }`). Using the + derived type allows the compiler to de-virtualize and allows for custom + functions not defined by base interface. Using base interface allows for + multiple formats with minimal instruction count. */ + +namespace wire +{ + //! Interface for converting "wire" (byte) formats to C/C++ objects without a DOM. + class reader + { + std::size_t depth_; //!< Tracks number of recursive objects and arrays + + //! \throw wire::exception if max depth is reached + void increment_depth(); + //! \throw std::logic_error if already `depth() == 0`. + void decrement_depth(); + + /*! \param min_element_size of each array element in any format - if known. + Derived types with explicit element count should verify available + space, and throw a `wire::exception` on issues. + \throw wire::exception if next value not array + \throw wire::exception if not enough bytes for all array elements + (with epee/msgpack which has specified number of elements). + \return Number of values to read before calling `is_array_end()`. */ + virtual std::size_t do_start_array(std::size_t min_element_size) = 0; + + //! \throw wire::exception if not object begin. \return State to be given to `key(...)` function. + virtual std::size_t do_start_object() = 0; + + protected: + epee::span remaining_; //!< Derived class tracks unprocessed bytes here + + reader(const epee::span remaining) noexcept + : depth_(0), remaining_(remaining) + {} + + reader(const reader&) = delete; + reader(reader&&) = delete; + reader& operator=(const reader&) = delete; + reader& operator=(reader&&) = delete; + + public: + struct key_map + { + const char* name; + unsigned id; // remaining() const noexcept { return remaining_; } + + //! \throw wire::exception if parsing is incomplete. + virtual void check_complete() const = 0; + + //! \throw wire::exception if array, object, or end of stream. + virtual basic_value basic() = 0; + + //! \throw wire::exception if next value not a boolean. + virtual bool boolean() = 0; + + //! \throw wire::expception if next value not an integer. + virtual std::intmax_t integer() = 0; + + //! \throw wire::exception if next value not an unsigned integer. + virtual std::uintmax_t unsigned_integer() = 0; + + //! \throw wire::exception if next value not number + virtual double real() = 0; + + //! throw wire::exception if next value not string + virtual std::string string() = 0; + + /*! Copy upcoming string directly into `dest`. + \throw wire::exception if next value not string + \throw wire::exception if next string exceeds `dest.size())` + \throw wire::exception if `exact == true` and next string is not `dest.size()` + \return Number of bytes read into `dest`. */ + virtual std::size_t string(epee::span dest, bool exact) = 0; + + // ! \throw wire::exception if next value cannot be read as binary + virtual epee::byte_slice binary() = 0; + + /*! Copy upcoming binary directly into `dest`. + \throw wire::exception if next value not binary + \throw wire::exception if next binary exceeds `dest.size()` + \throw wire::exception if `exact == true` and next binary is not `dest.size()`. + \return Number of bytes read into `dest`. */ + virtual std::size_t binary(epee::span dest, const bool exact) = 0; + + /*! \param min_element_size of each array element in any format - if known. + Derived types with explicit element count should verify available + space, and throw a `wire::exception` on issues. + \throw wire::exception if next value not array + \throw wire::exception if not enough bytes for all array elements + (with epee/msgpack which has specified number of elements). + \return Number of values to read before calling `is_array_end()`. */ + std::size_t start_array(std::size_t min_element_size); + + //! \return True if there is another element to read. + virtual bool is_array_end(std::size_t count) = 0; + + void end_array() { decrement_depth(); } + + + //! \throw wire::exception if not object begin. \return State to be given to `key(...)` function. + std::size_t start_object(); + + /*! Read a key of an object field and match against a known list of keys. + Skips or throws exceptions on unknown fields depending on implementation + settings. + + \param map of known keys (strings and integer) that are valid. + \param[in,out] state returned by `start_object()` or `key(...)` whichever + was last. + \param[out] index of match found in `map`. + + \throw wire::exception if next value not a key. + \throw wire::exception if next key not found in `map` and skipping + fields disabled. + + \return True if this function found a field in `map` to process. + */ + virtual bool key(epee::span map, std::size_t& state, std::size_t& index) = 0; + + void end_object() { decrement_depth(); } + }; + + template + inline void read_bytes(R& source, bool& dest) + { dest = source.boolean(); } + + template + inline void read_bytes(R& source, double& dest) + { dest = source.real(); } + + template + inline void read_bytes(R& source, std::string& dest) + { dest = source.string(); } + + template + inline void read_bytes(R& source, epee::byte_slice& dest) + { dest = source.binary(); } + + template + inline std::enable_if_t::value> read_bytes(R& source, T& dest) + { source.binary(epee::as_mut_byte_span(dest), /*exact=*/ true); } + + //! Use `read_bytes(...)` method if available for `T`. + template + inline auto read_bytes(R& source, T& dest) -> decltype(dest.read_bytes(source)) + { return dest.read_bytes(source); } + + namespace integer + { + [[noreturn]] void throw_exception(std::intmax_t value, std::intmax_t min, std::intmax_t max); + [[noreturn]] void throw_exception(std::uintmax_t value, std::uintmax_t max); + + template + inline T cast_signed(const U source) + { + using limit = std::numeric_limits; + static_assert( + std::is_signed::value && std::is_integral::value, + "target must be signed integer type" + ); + static_assert( + std::is_signed::value && std::is_integral::value, + "source must be signed integer type" + ); + if (source < limit::min() || limit::max() < source) + throw_exception(source, limit::min(), limit::max()); + return static_cast(source); + } + + template + inline T cast_unsigned(const U source) + { + using limit = std::numeric_limits; + static_assert( + std::is_unsigned::value && std::is_integral::value, + "target must be unsigned integer type" + ); + static_assert( + std::is_unsigned::value && std::is_integral::value, + "source must be unsigned integer type" + ); + if (limit::max() < source) + throw_exception(source, limit::max()); + return static_cast(source); + } + } // integer + + //! read all current and future signed integer types + 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 std::enable_if_t::value && std::is_integral::value> + read_bytes(R& source, T& dest) + { + dest = integer::cast_unsigned(source.unsigned_integer()); + } +} // wire + +namespace wire_read +{ + /*! Don't add a function called `read_bytes` to this namespace, it will + prevent 2-phase lookup. 2-phase lookup delays the function searching until + the template is used instead of when its defined. This allows the + unqualified calls to `read_bytes` in this namespace to "find" user + functions that are declared after these functions (the technique behind + `boost::serialization`). */ + + [[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) + } + + //! Use `source` to store information at `dest` + template + inline std::error_code from_bytes(T&& source, U& dest) + { + if (wire::is_optional_root::value && source.empty()) + return {}; + + try + { + R in{std::forward(source)}; + bytes(in, dest); + in.check_complete(); + } + catch (const wire::exception& e) + { + return e.code(); + } + + return {}; + } + + // Trap objects that do not have standard insertion functions + template + void array_insert(const R&, const T&...) noexcept + { + static_assert(std::is_same::value, "type T does not have a valid insertion function"); + } + + // Insert to sorted containers + template + inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_hint(dest.end(), std::declval()), bool(true)) + { + V val{}; + wire_read::bytes(source, val); + dest.emplace_hint(dest.end(), std::move(val)); + return true; + } + + // Insert into unsorted containers + template + inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_back(), dest.back(), bool(true)) + { + // more efficient to process the object in-place in many cases + dest.emplace_back(); + wire_read::bytes(source, dest.back()); + return true; + } + + // no compile-time checks for the array constraints + template + inline void array_unchecked(R& source, T& dest, const std::size_t min_element_size, const std::size_t max_element_count) + { + using value_type = typename T::value_type; + static_assert(!std::is_same::value, "read array of chars as string"); + static_assert(!std::is_same::value, "read array of signed chars as binary"); + static_assert(!std::is_same::value, "read array of unsigned chars as binary"); + + std::size_t count = source.start_array(min_element_size); + + // quick check for epee/msgpack formats + if (max_element_count < count) + throw_exception(wire::error::schema::array_max_element, "", nullptr); + + dest.clear(); + wire::reserve(dest, count); + + bool more = count; + const std::size_t start_bytes = source.remaining().size(); + while (more || !source.is_array_end(count)) + { + // check for json/cbor formats + if (source.delimited_arrays() && max_element_count <= dest.size()) + throw_exception(wire::error::schema::array_max_element, "", nullptr); + + wire_read::array_insert(source, dest); + --count; + more &= bool(count); + + if (((start_bytes - source.remaining().size()) / dest.size()) < min_element_size) + throw_exception(wire::error::schema::array_min_size, "", nullptr); + } + + source.end_array(); + } + + template::max()> + inline void array(R& source, T& dest, wire::min_element_size min_element_size, wire::max_element_count max_element_count = {}) + { + using value_type = typename T::value_type; + static_assert( + min_element_size.template check() || max_element_count.template check(), + "array unpacking memory issues" + ); + // each set of template args generates unique ASM, merge them down + array_unchecked(source, dest, min_element_size, max_element_count); + } + + template + inline void reset_field(wire::field_& dest) + { + // array fields are always optional, see `wire/field.h` + if (dest.optional_on_empty()) + wire::clear(dest.get_value()); + } + + template + inline void reset_field(wire::field_& dest) + { + dest.get_value().reset(); + } + + template + inline void unpack_field(R& source, wire::field_& dest) + { + bytes(source, dest.get_value()); + } + + template + inline void unpack_field(R& source, wire::field_& dest) + { + if (!bool(dest.get_value())) + dest.get_value().emplace(); + bytes(source, *dest.get_value()); + } + + //! Tracks read status of every object field instance. + template + class tracker + { + T field_; + std::size_t our_index_; + bool read_; + + public: + static constexpr bool is_required() noexcept { return T::is_required(); } + static constexpr std::size_t count() noexcept { return T::count(); } + + explicit tracker(T field) + : field_(std::move(field)), our_index_(0), read_(false) + {} + + //! \return Field name if required and not read, otherwise `nullptr`. + const char* name_if_missing() const noexcept + { + return (is_required() && !read_) ? field_.name : nullptr; + } + + //! Set all entries in `map` related to this field (expand variant types!). + template + std::size_t set_mapping(std::size_t index, wire::reader::key_map (&map)[N]) noexcept + { + our_index_ = index; + map[index].id = field_.id(); + map[index].name = field_.name; + return index + count(); + } + + //! Try to read next value if `index` matches `this`. \return 0 if no match, 1 if optional field read, and 2 if required field read + template + std::size_t try_read(R& source, const std::size_t index) + { + if (index < our_index_ || our_index_ + count() <= index) + return 0; + if (read_) + throw_exception(wire::error::schema::invalid_key, "duplicate", {std::addressof(field_.name), 1}); + + unpack_field(source, field_); + read_ = true; + return 1 + is_required(); + } + + //! Reset optional fields that were skipped + bool reset_omitted() + { + if (!is_required() && !read_) + reset_field(field_); + return true; + } + }; + + // `expand_tracker_map` writes all `tracker` types to a table + + template + inline void expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N]) noexcept + {} + + template + inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker& head, tracker&... tail) noexcept + { + expand_tracker_map(head.set_mapping(index, map), map, tail...); + } + + template + inline void object(R& source, tracker... fields) + { + static constexpr const std::size_t total_subfields = wire::sum(fields.count()...); + static_assert(total_subfields < 100, "algorithm uses too much stack space and linear searching"); + static_assert(sizeof...(T) <= total_subfields, "subfield parameter pack size mismatch"); + + std::size_t state = source.start_object(); + std::size_t required = wire::sum(std::size_t(fields.is_required())...); + + wire::reader::key_map map[total_subfields + 1] = {}; // +1 for empty object + expand_tracker_map(0, map, fields...); + + std::size_t next = 0; + while (source.key({map, total_subfields}, state, next)) + { + switch (wire::sum(fields.try_read(source, next)...)) + { + default: + case 0: + throw_exception(wire::error::schema::invalid_key, "bad map setup", nullptr); + break; + case 2: + --required; /* fallthrough */ + case 1: + break; + } + } + + if (required) + { + const char* missing[] = {fields.name_if_missing()..., nullptr}; + throw_exception(wire::error::schema::missing_key, "", missing); + } + + wire::sum(fields.reset_omitted()...); + source.end_object(); + } +} // wire_read + +namespace wire +{ + template + inline std::enable_if_t::value> read_bytes(R& source, T& dest) + { + static constexpr const std::size_t wire_size = + default_min_element_size::value; + static_assert( + wire_size != 0, + "no sane default array constraints for the reader / value_type pair" + ); + + wire_read::array(source, dest, min_element_size{}); + } + + template + inline std::enable_if_t::value> object(R& source, T... fields) + { + wire_read::object(source, wire_read::tracker{std::move(fields)}...); + } + + template + inline void object_fwd(const std::true_type /*is_read*/, R& source, T&&... fields) + { + wire::object(source, std::forward(fields)...); + } +} diff --git a/contrib/epee/include/serialization/wire/wrapper/array.h b/contrib/epee/include/serialization/wire/wrapper/array.h new file mode 100644 index 000000000..1f75bbdfb --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/array.h @@ -0,0 +1,161 @@ +// 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 "serialization/wire/field.h" +#include "serialization/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/contrib/epee/include/serialization/wire/wrapper/array_blob.h b/contrib/epee/include/serialization/wire/wrapper/array_blob.h new file mode 100644 index 000000000..3614a896e --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/array_blob.h @@ -0,0 +1,95 @@ +// 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 + +#include "serialization/wire/field.h" +#include "serialization/wire/traits.h" + +/*! A required field, where the array contents are written as a single binary + blob. All (empty) arrays-as-blobs were "optional" (omitted) historically in + epee, so this matches prior behavior. */ +#define WIRE_FIELD_ARRAY_AS_BLOB(name) \ + ::wire::optional_field( #name , ::wire::array_as_blob(std::ref( self . name ))) + +namespace wire +{ + /*! A wrapper that tells `wire::writer`s` and `wire::reader`s to encode a + container as a single binary blob. This wrapper meets the requirements for + an optional field - currently the type is always considered optional by + the input/output system to match the old input/output engine. + + `container_type` is `T` with optional `std::reference_wrapper` removed. + `container_type` concept requirements: + * `typedef` `value_type` that specifies inner type. + * `std::is_pod::value` must be true. + Additional concept requirements for `container_type` when reading: + * must have `clear()` method that removes all elements (`size() == 0`). + * must have `emplace_back()` method that default initializes new element + * must have `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 have `size()` method that returns number of elements. */ + template + struct array_as_blob_ + { + using container_type = unwrap_reference_t; + using value_type = typename container_type::value_type; + static constexpr std::size_t value_size() noexcept { return sizeof(value_type); } + static_assert(std::is_pod::value, "container value must be POD"); + + T container; + + constexpr const container_type& get_container() const noexcept { return container; } + container_type& get_container() noexcept { return container; } + + // concept requirements for optional fields + + explicit operator bool() const noexcept { return !get_container().empty(); } + container_type& emplace() noexcept { return get_container(); } + + array_as_blob_& operator*() noexcept { return *this; } + const array_as_blob_& operator*() const noexcept { return *this; } + + void reset() { get_container().clear(); } + }; + + template + inline array_as_blob_ array_as_blob(T value) + { + return {std::move(value)}; + } + + // `read_bytes` / `write_bytes` in `wire/wrappers_impl.h` + + // Do not specialize `is_optional_on_empty`; allow selection + +} // wire diff --git a/contrib/epee/include/serialization/wire/wrapper/blob.h b/contrib/epee/include/serialization/wire/wrapper/blob.h new file mode 100644 index 000000000..cab720276 --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/blob.h @@ -0,0 +1,87 @@ +// 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 "serialization/wire/field.h" +#include "serialization/wire/traits.h" +#include "span.h" + +// +// Also see `wire/traits.h` to register a type as a blob (no wrapper needed). +// + + +/*! A required field, where the wire content is expected to be a binary blob, + and the C++ data is a pod type with no padding. */ +#define WIRE_FIELD_BLOB(name) \ + ::wire::field( #name , ::wire::blob(std::ref( self . name ))) + +namespace wire +{ + /*! A wrapper that tells `wire::writer`s` and `wire::reader`s to encode a + type as a binary blob. If the encoded size on the wire is not exactly the + size of the blob, it is considered an error. + + `value_type` is `T` with optional `std::reference_wrapper` removed. + `value_type` concept requirements: + * `epee::has_padding()` must return false. */ + template + struct blob_ + { + using value_type = unwrap_reference_t; + static_assert(!epee::has_padding(), "expected safe pod type"); + + 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; } + }; + + template + constexpr inline blob_ blob(T value) + { + return {std::move(value)}; + } + + template + inline void read_bytes(R& source, blob_ dest) + { + source.binary(epee::as_mut_byte_span(dest.get_value()), /*exact=*/ true); + } + + template + inline void write_bytes(W& dest, const blob_ source) + { + dest.binary(epee::as_byte_span(source.get_value())); + } +} // wire diff --git a/contrib/epee/include/serialization/wire/wrapper/variant.h b/contrib/epee/include/serialization/wire/wrapper/variant.h new file mode 100644 index 000000000..29d716983 --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/variant.h @@ -0,0 +1,205 @@ +// 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 "serialization/wire/error.h" +#include "serialization/wire/fwd.h" +#include "serialization/wire/field.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/contrib/epee/src/wire/asio.cpp b/contrib/epee/src/wire/asio.cpp new file mode 100644 index 000000000..e9042c2ac --- /dev/null +++ b/contrib/epee/src/wire/asio.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2021-2023, 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 "serialization/wire/adapted/asio.h" + +#include "serialization/wire.h" +#include "span.h" + +namespace wire +{ + void read_bytes(reader& source, boost::asio::ip::address_v6& dest) + { + boost::asio::ip::address_v6::bytes_type value{}; + source.binary(epee::to_mut_span(value), true); + dest = boost::asio::ip::address_v6(value); + } + + void write_bytes(writer& dest, const boost::asio::ip::address_v6& source) + { + const auto bytes = source.to_bytes(); + dest.binary(epee::to_span(bytes)); + } +} diff --git a/contrib/epee/src/wire/basic_value.cpp b/contrib/epee/src/wire/basic_value.cpp new file mode 100644 index 000000000..98e9a0c85 --- /dev/null +++ b/contrib/epee/src/wire/basic_value.cpp @@ -0,0 +1,55 @@ +// 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 "serialization/wire/basic_value.h" + +#include +#include +#include "serialization/wire/read.h" +#include "serialization/wire/write.h" + +namespace wire +{ + static void write_bytes(writer& dest, const std::nullptr_t&) + { + throw std::logic_error{"nullptr output not yet defined for wire::writer"}; + } + + void basic_value::reset() + { + value = nullptr; + } + + void read_bytes(reader& source, basic_value& dest) + { + dest = source.basic(); + } + void write_bytes(writer& dest, const basic_value& source) + { + boost::apply_visitor([&dest] (const auto& val) { wire_write::bytes(dest, val); }, source.value); + } +} diff --git a/contrib/epee/src/wire/read.cpp b/contrib/epee/src/wire/read.cpp new file mode 100644 index 000000000..c717b57e0 --- /dev/null +++ b/contrib/epee/src/wire/read.cpp @@ -0,0 +1,88 @@ +// 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. + +#include "serialization/wire/read.h" + +#include + +void wire::reader::increment_depth() +{ + if (++depth_ == max_read_depth()) + WIRE_DLOG_THROW_(error::schema::maximum_depth); +} + +void wire::reader::decrement_depth() +{ + if (!depth_) + throw std::logic_error{"reader::decrement_depth() already at zero"}; + --depth_; +} + +std::size_t wire::reader::start_array(std::size_t min_element_size) +{ + increment_depth(); + return do_start_array(min_element_size); +} + +std::size_t wire::reader::start_object() +{ + increment_depth(); + return do_start_object(); +} + +[[noreturn]] void wire::integer::throw_exception(const std::intmax_t source, const std::intmax_t min, const std::intmax_t max) +{ + static_assert(0 <= std::numeric_limits::max(), "expected 0 <= intmax_t::max"); + static_assert( + std::numeric_limits::max() <= std::numeric_limits::max(), + "expected intmax_t::max <= uintmax_t::max" + ); + if (source < 0) + WIRE_DLOG_THROW(error::schema::larger_integer, source << " given when " << min << " is minimum permitted"); + else + throw_exception(std::uintmax_t(source), std::uintmax_t(max)); +} +[[noreturn]] void wire::integer::throw_exception(const std::uintmax_t source, const std::uintmax_t max) +{ + WIRE_DLOG_THROW(error::schema::smaller_integer, source << " given when " << max << " is maximum permitted"); +} + +[[noreturn]] void wire_read::throw_exception(const wire::error::schema code, const char* display, epee::span names) +{ + const char* name = nullptr; + for (const char* elem : names) + { + if (elem != nullptr) + { + name = elem; + break; + } + } + WIRE_DLOG_THROW(code, display << (name ? name : "")); +} + +