Read interface for new serialization system

This commit is contained in:
Lee *!* Clagett 2023-08-11 15:23:54 -04:00 committed by Lee Clagett
parent cc73fe7116
commit 510010cf0c
13 changed files with 1529 additions and 7 deletions

View File

@ -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 <boost/asio/ip/address_v6.hpp>
#include "serialization/wire/fwd.h"
namespace wire
{
WIRE_DECLARE_OBJECT(boost::asio::ip::address_v6);
}

View File

@ -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 <cstdint>
#include <boost/container/static_vector.hpp>
#include <boost/utility/string_ref.hpp>
#include "serialization/wire/read.h"
namespace wire
{
// enable writing of static vector arrays
template<typename T, std::size_t N>
struct is_array<boost::container::static_vector<T, N>>
: std::true_type
{};
// `static_vector`s of `char` and `uint8_t` are not arrays
template<std::size_t N>
struct is_array<boost::container::static_vector<char, N>>
: std::false_type
{};
template<std::size_t N>
struct is_array<boost::container::static_vector<std::uint8_t, N>>
: std::false_type
{};
// `static_vector` can be used without specialized macro for every type, it provides max element count
template<typename R, typename T, std::size_t N>
inline void read_bytes(R& source, boost::container::static_vector<T, N>& dest)
{
wire_read::array(source, dest, min_element_size<0>{}, max_element_count<N>{});
}
/* `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<typename R, std::size_t N>
inline void read_bytes(R& source, boost::container::static_vector<char, N>& dest)
{
dest.resize(N);
dest.resize(source.string(epee::to_mut_span(dest), /* exact= */ false));
}
template<typename W, std::size_t N>
inline void write_bytes(W& dest, const boost::container::static_vector<char, N>& source)
{
dest.string(boost::string_ref{source.data(), source.size()});
}
template<typename R, std::size_t N>
inline void read_bytes(R& source, boost::container::static_vector<std::uint8_t, N>& dest)
{
dest.resize(N);
dest.resize(source.binary(epee::to_mut_span(dest), /* exact= */ false));
}
template<typename W, std::size_t N>
inline void write_bytes(W& dest, const boost::container::static_vector<std::uint8_t, N>& source)
{
dest.binary(epee::to_span(source));
}
}

View File

@ -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 <unordered_map>
#include "serialization/wire/write.h"
namespace wire
{
// no `read_bytes`, requires implementation with "array read constraints"
template<typename W, typename K, typename V, typename H, typename E, typename A>
inline void write_bytes(W& dest, const std::unordered_map<K, V, H, E, A>& source)
{ wire_write::dynamic_object(dest, source); }
}

View File

@ -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 <boost/variant/variant.hpp>
#include <cstddef>
#include <cstdint>
#include <string>
#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<std::nullptr_t, bool, std::uintmax_t, std::intmax_t, double, std::string>;
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

View File

@ -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<typename T, bool Required>
template<typename T, bool Required, unsigned I = 0>
struct field_
{
using value_type = unwrap_reference_t<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<typename T>
constexpr inline field_<T, true> field(const char* name, T value)
template<unsigned I = 0, typename T = void>
constexpr inline field_<T, true, I> field(const char* name, T value)
{
return {name, std::move(value)};
}
//! Links `name` to optional `value`. Use `std::ref` if de-serializing.
template<typename T>
constexpr inline field_<T, false> optional_field(const char* name, T value)
template<unsigned I = 0, typename T = void>
constexpr inline field_<T, false, I> optional_field(const char* name, T value)
{
return {name, std::move(value)};
}

View File

@ -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 <cstdint>
#include <limits>
#include <string>
#include <type_traits>
#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<ns::type> : 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<const std::uint8_t> remaining_; //!< Derived class tracks unprocessed bytes here
reader(const epee::span<const std::uint8_t> 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; //<! For integer key formats;
};
//! \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 {}; }
virtual ~reader() noexcept
{}
//! \return Number of recursive objects and arrays
std::size_t depth() const noexcept { return depth_; }
//! \return Unprocessed bytes
epee::span<const std::uint8_t> 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<char> 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<std::uint8_t> 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<const key_map> map, std::size_t& state, std::size_t& index) = 0;
void end_object() { decrement_depth(); }
};
template<typename R>
inline void read_bytes(R& source, bool& dest)
{ dest = source.boolean(); }
template<typename R>
inline void read_bytes(R& source, double& dest)
{ dest = source.real(); }
template<typename R>
inline void read_bytes(R& source, std::string& dest)
{ dest = source.string(); }
template<typename R>
inline void read_bytes(R& source, epee::byte_slice& dest)
{ dest = source.binary(); }
template<typename R, typename T>
inline std::enable_if_t<is_blob<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<typename R, typename T>
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<typename T, typename U>
inline T cast_signed(const U source)
{
using limit = std::numeric_limits<T>;
static_assert(
std::is_signed<T>::value && std::is_integral<T>::value,
"target must be signed integer type"
);
static_assert(
std::is_signed<U>::value && std::is_integral<U>::value,
"source must be signed integer type"
);
if (source < limit::min() || limit::max() < source)
throw_exception(source, limit::min(), limit::max());
return static_cast<T>(source);
}
template<typename T, typename U>
inline T cast_unsigned(const U source)
{
using limit = std::numeric_limits<T>;
static_assert(
std::is_unsigned<T>::value && std::is_integral<T>::value,
"target must be unsigned integer type"
);
static_assert(
std::is_unsigned<U>::value && std::is_integral<U>::value,
"source must be unsigned integer type"
);
if (limit::max() < source)
throw_exception(source, limit::max());
return static_cast<T>(source);
}
} // integer
//! read all current and future signed integer types
template<typename R, typename T>
inline std::enable_if_t<std::is_signed<T>::value && std::is_integral<T>::value>
read_bytes(R& source, T& dest)
{
dest = integer::cast_signed<T>(source.integer());
}
//! read all current and future unsigned integer types
template<typename R, typename T>
inline std::enable_if_t<std::is_unsigned<T>::value && std::is_integral<T>::value>
read_bytes(R& source, T& dest)
{
dest = integer::cast_unsigned<T>(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<char const* const> name_list);
template<typename R, typename T>
inline void bytes(R& source, T&& dest)
{
read_bytes(source, dest); // ADL (searches every associated namespace)
}
//! Use `source` to store information at `dest`
template<typename R, typename T, typename U>
inline std::error_code from_bytes(T&& source, U& dest)
{
if (wire::is_optional_root<U>::value && source.empty())
return {};
try
{
R in{std::forward<T>(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<typename R, typename... T>
void array_insert(const R&, const T&...) noexcept
{
static_assert(std::is_same<R, void>::value, "type T does not have a valid insertion function");
}
// Insert to sorted containers
template<typename R, typename T, typename V = typename T::value_type>
inline auto array_insert(R& source, T& dest) -> decltype(dest.emplace_hint(dest.end(), std::declval<V>()), bool(true))
{
V val{};
wire_read::bytes(source, val);
dest.emplace_hint(dest.end(), std::move(val));
return true;
}
// Insert into unsorted containers
template<typename R, typename T>
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<typename R, typename T>
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_type, char>::value, "read array of chars as string");
static_assert(!std::is_same<value_type, std::int8_t>::value, "read array of signed chars as binary");
static_assert(!std::is_same<value_type, std::uint8_t>::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<typename R, typename T, std::size_t M, std::size_t N = std::numeric_limits<std::size_t>::max()>
inline void array(R& source, T& dest, wire::min_element_size<M> min_element_size, wire::max_element_count<N> max_element_count = {})
{
using value_type = typename T::value_type;
static_assert(
min_element_size.template check<value_type>() || max_element_count.template check<value_type>(),
"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<typename T, unsigned I>
inline void reset_field(wire::field_<T, true, I>& dest)
{
// array fields are always optional, see `wire/field.h`
if (dest.optional_on_empty())
wire::clear(dest.get_value());
}
template<typename T, unsigned I>
inline void reset_field(wire::field_<T, false, I>& dest)
{
dest.get_value().reset();
}
template<typename R, typename T>
inline void unpack_field(R& source, wire::field_<T, true>& dest)
{
bytes(source, dest.get_value());
}
template<typename R, typename T>
inline void unpack_field(R& source, wire::field_<T, false>& dest)
{
if (!bool(dest.get_value()))
dest.get_value().emplace();
bytes(source, *dest.get_value());
}
//! Tracks read status of every object field instance.
template<typename T>
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 N>
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<typename R>
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<std::size_t N>
inline void expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N]) noexcept
{}
template<std::size_t N, typename T, typename... U>
inline void expand_tracker_map(std::size_t index, wire::reader::key_map (&map)[N], tracker<T>& head, tracker<U>&... tail) noexcept
{
expand_tracker_map(head.set_mapping(index, map), map, tail...);
}
template<typename R, typename... T>
inline void object(R& source, tracker<T>... 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<typename R, typename T>
inline std::enable_if_t<is_array<T>::value> read_bytes(R& source, T& dest)
{
static constexpr const std::size_t wire_size =
default_min_element_size<R, typename T::value_type>::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<wire_size>{});
}
template<typename R, typename... T>
inline std::enable_if_t<std::is_base_of<reader, R>::value> object(R& source, T... fields)
{
wire_read::object(source, wire_read::tracker<T>{std::move(fields)}...);
}
template<typename R, typename... T>
inline void object_fwd(const std::true_type /*is_read*/, R& source, T&&... fields)
{
wire::object(source, std::forward<T>(fields)...);
}
}

View File

@ -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 <cstdint>
#include <utility>
#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<typename T, typename C = void>
struct array_
{
using constraint = C;
using container_type = unwrap_reference_t<T>;
using value_type = typename container_type::value_type;
// See nested `array_` overload below
using inner_array = std::reference_wrapper<value_type>;
using inner_array_const = std::reference_wrapper<const value_type>;
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<typename T, typename C, typename D>
struct array_<array_<T, C>, D>
{
// compute `container_type` and `value_type` recursively
using constraint = D;
using container_type = typename array_<T, C>::container_type;
using value_type = typename container_type::value_type;
// Re-compute `array_` for inner values
using inner_array = array_<typename array_<T, C>::inner_array, C>;
using inner_array_const = array_<typename array_<T, C>::inner_array_const, C>;
array_<T, C> 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<typename C = void, typename T = void>
inline constexpr array_<T, C> 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

View File

@ -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 <cstdint>
#include <type_traits>
#include <utility>
#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_type>::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<typename T>
struct array_as_blob_
{
using container_type = unwrap_reference_t<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_type>::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<typename T>
inline array_as_blob_<T> 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

View File

@ -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 <type_traits>
#include <utility>
#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<value_type>()` must return false. */
template<typename T>
struct blob_
{
using value_type = unwrap_reference_t<T>;
static_assert(!epee::has_padding<value_type>(), "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<typename T>
constexpr inline blob_<T> blob(T value)
{
return {std::move(value)};
}
template<typename R, typename T>
inline void read_bytes(R& source, blob_<T> dest)
{
source.binary(epee::as_mut_byte_span(dest.get_value()), /*exact=*/ true);
}
template<typename W, typename T>
inline void write_bytes(W& dest, const blob_<T> source)
{
dest.binary(epee::as_byte_span(source.get_value()));
}
} // wire

View File

@ -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 <boost/variant/get.hpp>
#include <boost/variant/variant_fwd.hpp>
#include <functional>
#include <utility>
#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<type>(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<typename F, typename T>
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<typename T>
struct variant_
{
using variant_type = unwrap_reference_t<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<typename U>
variant_& operator=(U&& rhs)
{
get_variant() = std::forward<U>(rhs);
return *this;
}
};
template<typename T>
inline constexpr variant_<T> variant(T value)
{ return {std::move(value)}; }
namespace adapt
{
template<typename T>
inline void throw_if_not_read(const T&)
{ throw_variant_exception(error::schema::missing_key, typeid(T).name()); }
template<typename T>
inline void throw_if_not_read(const variant_<T>& value)
{
if (!value.read)
value.throw_exception(error::schema::missing_key);
}
// other variant overloads can be added here as needed
template<typename U, typename... T>
inline const U* get_if(const boost::variant<T...>& value)
{ return boost::get<U>(std::addressof(value)); }
template<typename U, typename T>
inline const U* get_if(const variant_<T>& value)
{ return adapt::get_if<U>(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<U>` 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<U>` 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<U>` and `get_if<U>` 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<U>(...)` or `WIRE_OPTION` macro - only one
type is active so `wire::optional_field` will omit all other types/fields. */
template<typename T, typename U>
struct option_
{
using variant_type = unwrap_reference_t<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<U>(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<typename U, typename T>
inline constexpr option_<T, U> option(T value)
{ return {std::move(value)}; }
namespace adapt
{
// other variant overloads can be added here as needed
template<typename U, typename... T>
inline U& get(boost::variant<T...>& value)
{ return boost::get<U>(value); }
template<typename U, typename... T>
inline const U& get(const boost::variant<T...>& value)
{ return boost::get<U>(value); }
template<typename U, typename T>
inline const U& get(const variant_<T>& value)
{ return adapt::get<U>(value.get_variant()); }
}
//! \throw wire::exception if `dest.get_variant()` has already been used in `read_bytes`.
template<typename R, typename T, typename U>
inline void read_bytes(R& source, option_<std::reference_wrapper<variant_<T>>, U> dest)
{
if (dest.get_variant().read)
dest.get_variant().throw_exception(error::schema::invalid_key);
wire_read::bytes(source, adapt::get<U>(dest.get_variant().get_variant()));
dest.get_variant().read = true;
}
template<typename W, typename T, typename U>
inline void write_bytes(W& dest, const option_<T, U>& source)
{ wire_write::bytes(dest, adapt::get<U>(source.get_variant())); }
}

View File

@ -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));
}
}

View File

@ -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 <boost/variant/apply_visitor.hpp>
#include <stdexcept>
#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);
}
}

View File

@ -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 <stdexcept>
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<std::intmax_t>::max(), "expected 0 <= intmax_t::max");
static_assert(
std::numeric_limits<std::intmax_t>::max() <= std::numeric_limits<std::uintmax_t>::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<char const* const> names)
{
const char* name = nullptr;
for (const char* elem : names)
{
if (elem != nullptr)
{
name = elem;
break;
}
}
WIRE_DLOG_THROW(code, display << (name ? name : ""));
}