Initial working base separated from top-level monero project

This commit is contained in:
Lee Clagett
2020-08-19 18:29:32 -04:00
commit a2ff89bc24
68 changed files with 11543 additions and 0 deletions

36
src/wire/CMakeLists.txt Normal file
View File

@@ -0,0 +1,36 @@
# Copyright (c) 2020, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws-wire_sources error.cpp read.cpp write.cpp)
set(monero-lws-wire_headers crypto.h error.h field.h filters.h fwd.h json.h read.h traits.h vector.h write.h)
add_library(monero-lws-wire ${monero-lws-wire_sources} ${monero-lws-wire_headers})
target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(monero-lws-wire PRIVATE monero::libraries)
add_subdirectory(json)

72
src/wire/crypto.h Normal file
View File

@@ -0,0 +1,72 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <type_traits>
#include "crypto/crypto.h" // monero/src
#include "ringct/rctTypes.h" // monero/src
#include "wire/traits.h"
namespace wire
{
template<>
struct is_blob<crypto::ec_scalar>
: std::true_type
{};
template<>
struct is_blob<crypto::hash>
: std::true_type
{};
template<>
struct is_blob<crypto::key_derivation>
: std::true_type
{};
template<>
struct is_blob<crypto::key_image>
: std::true_type
{};
template<>
struct is_blob<crypto::public_key>
: std::true_type
{};
template<>
struct is_blob<crypto::signature>
: std::true_type
{};
template<>
struct is_blob<rct::key>
: std::true_type
{};
}

93
src/wire/error.cpp Normal file
View File

@@ -0,0 +1,93 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "wire/error.h"
namespace wire
{
namespace error
{
const char* get_string(const schema value) noexcept
{
switch (value)
{
default:
break;
case schema::none:
return "No schema errors";
case schema::array:
return "Schema expected array";
case schema::binary:
return "Schema expected binary value of variable size";
case schema::boolean:
return "Schema expected boolean value";
case schema::enumeration:
return "Schema expected a specific of enumeration value(s)";
case schema::fixed_binary:
return "Schema expected binary of fixed size";
case schema::integer:
return "Schema expected integer value";
case schema::invalid_key:
return "Schema does not allow object field key";
case schema::larger_integer:
return "Schema expected a larger integer value";
case schema::maximum_depth:
return "Schema hit maximum array+object depth tracking";
case schema::missing_key:
return "Schema missing required field key";
case schema::number:
return "Schema expected number (integer or float) value";
case schema::object:
return "Schema expected object";
case schema::smaller_integer:
return "Schema expected a smaller integer value";
case schema::string:
return "Schema expected string";
}
return "Unknown schema error";
}
const std::error_category& schema_category() noexcept
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "wire::error::schema_category()";
}
virtual std::string message(int value) const override final
{
return get_string(schema(value));
}
};
static const category instance{};
return instance;
}
}
}

135
src/wire/error.h Normal file
View File

@@ -0,0 +1,135 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <exception>
#include <system_error>
#include <type_traits>
#include "misc_log_ex.h" // monero/contrib/epee/include
//! Print default `code` message followed by optional message to debug log then throw `code`.
#define WIRE_DLOG_THROW_(code, ...) \
do \
{ \
MDEBUG( get_string(code) __VA_ARGS__ ); \
throw ::wire::exception_t<decltype(code)>{code}; \
} \
while (0)
//! Print default `code` message followed by `msg` to debug log then throw `code`.
#define WIRE_DLOG_THROW(code, msg) \
WIRE_DLOG_THROW_(code, << ": " << msg)
namespace wire
{
namespace error
{
enum class schema : int
{
none = 0, //!< Must be zero for `expect<..>`
array, //!< Expected an array value
binary, //!< Expected a binary value of variable length
boolean, //!< Expected a boolean value
enumeration, //!< Expected a value from a specific set
fixed_binary, //!< Expected a binary value of fixed length
integer, //!< Expected an integer value
invalid_key, //!< Key for object is invalid
larger_integer, //!< Expected a larger integer value
maximum_depth, //!< Hit maximum number of object+array tracking
missing_key, //!< Missing required key for object
number, //!< Expected a number (integer or float) value
object, //!< Expected object value
smaller_integer, //!< Expected a smaller integer value
string, //!< Expected string value
};
//! \return Error message string.
const char* get_string(schema value) noexcept;
//! \return Category for `schema_error`.
const std::error_category& schema_category() noexcept;
//! \return Error code with `value` and `schema_category()`.
inline std::error_code make_error_code(const schema value) noexcept
{
return std::error_code{int(value), schema_category()};
}
} // error
//! `std::exception` doesn't require dynamic memory like `std::runtime_error`
struct exception : std::exception
{
exception() noexcept
: std::exception()
{}
exception(const exception&) = default;
exception& operator=(const exception&) = default;
virtual ~exception() noexcept
{}
virtual std::error_code code() const noexcept = 0;
};
template<typename T>
class exception_t final : public wire::exception
{
static_assert(std::is_enum<T>(), "only enumerated types allowed");
T value;
public:
exception_t(T value) noexcept
: value(value)
{}
exception_t(const exception_t&) = default;
~exception_t() = default;
exception_t& operator=(const exception_t&) = default;
const char* what() const noexcept override final
{
static_assert(noexcept(noexcept(get_string(value))), "get_string function must be noexcept");
return get_string(value);
}
std::error_code code() const noexcept override final
{
static_assert(noexcept(noexcept(make_error_code(value))), "make_error_code funcion must be noexcept");
return make_error_code(value);
}
};
}
namespace std
{
template<>
struct is_error_code_enum<wire::error::schema>
: true_type
{};
}

280
src/wire/field.h Normal file
View File

@@ -0,0 +1,280 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <functional>
#include <utility>
#include "wire/filters.h"
#include "wire/traits.h"
//! A required field has the same key name and C/C++ name
#define WIRE_FIELD(name) \
::wire::field( #name , std::ref( self . 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) \
::wire::field( #name , self . name )
//! The optional field has the same key name and C/C++ name
#define WIRE_OPTIONAL_FIELD(name) \
::wire::optional_field( #name , std::ref( self . name ))
namespace wire
{
template<typename T>
struct unwrap_reference
{
using type = T;
};
template<typename T>
struct unwrap_reference<std::reference_wrapper<T>>
{
using type = T;
};
//! Links `name` to a `value` for object serialization.
template<typename T, bool Required>
struct field_
{
using value_type = typename unwrap_reference<T>::type;
static constexpr bool is_required() noexcept { return Required; }
static constexpr std::size_t count() noexcept { return 1; }
const char* name;
T value;
//! \return `value` with `std::reference_wrapper` removed.
constexpr const value_type& get_value() const noexcept
{
return value;
}
//! \return `value` with `std::reference_wrapper` removed.
value_type& get_value() noexcept
{
return value;
}
};
//! Links `name` to `value`. Use `std::ref` if de-serializing.
template<typename T>
constexpr inline field_<T, true> field(const char* name, T value)
{
return {name, std::move(value)};
}
//! Links `name` to `value`. Use `std::ref` if de-serializing.
template<typename T>
constexpr inline field_<T, false> optional_field(const char* name, T value)
{
return {name, std::move(value)};
}
//! Links `name` to a type `T` for variant serialization.
template<typename T>
struct option
{
const char* name;
};
//! \return Name associated with type `T` for variant `field`.
template<typename T, typename U>
constexpr const char* get_option_name(const U& field) noexcept
{
return static_cast< const option<T>& >(field).name;
}
//! Links each type in a variant to a string key.
template<typename T, bool Required, typename... U>
struct variant_field_ : option<U>...
{
using value_type = typename unwrap_reference<T>::type;
static constexpr bool is_required() noexcept { return Required; }
static constexpr std::size_t count() noexcept { return sizeof...(U); }
constexpr variant_field_(const char* name, T value, option<U>... opts)
: option<U>(std::move(opts))..., name(name), value(std::move(value))
{}
const char* name;
T value;
constexpr const value_type& get_value() const noexcept
{
return value;
}
value_type& get_value() noexcept
{
return value;
}
template<typename V>
struct wrap
{
using result_type = void;
variant_field_ self;
V visitor;
template<typename X>
void operator()(const X& value) const
{
visitor(get_option_name<X>(self), value);
}
};
template<typename V>
void visit(V visitor) const
{
apply_visitor(wrap<V>{*this, std::move(visitor)}, get_value());
}
};
//! Links variant `value` to a unique name per type in `opts`. Use `std::ref` for `value` if de-serializing.
template<typename T, typename... U>
constexpr inline variant_field_<T, true, U...> variant_field(const char* name, T value, option<U>... opts)
{
return {name, std::move(value), std::move(opts)...};
}
//! Indicates a field value should be written as an array
template<typename T, typename F>
struct as_array_
{
using value_type = typename unwrap_reference<T>::type;
T value;
F filter; //!< Each element in `value` given to this callable before `write_bytes`.
//! \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;
}
};
//! Callable that can filter `as_object` values or be used immediately.
template<typename Default>
struct as_array_filter
{
Default default_filter;
template<typename T>
constexpr as_array_<T, Default> operator()(T value) const
{
return {std::move(value), default_filter};
}
template<typename T, typename F>
constexpr as_array_<T, F> operator()(T value, F filter) const
{
return {std::move(value), std::move(filter)};
}
};
//! Usage: `wire::field("foo", wire::as_array(self.foo, to_string{})`. Consider `std::ref`.
constexpr as_array_filter<identity_> as_array{};
//! Indicates a field value should be written as an object
template<typename T, typename F, typename G>
struct as_object_
{
using map_type = typename unwrap_reference<T>::type;
T map;
F key_filter; //!< Each key (`.first`) in `map` given to this callable before writing field key.
G value_filter; //!< Each value (`.second`) in `map` given to this callable before `write_bytes`.
//! \return `map` with `std::reference_wrapper` removed.
constexpr const map_type& get_map() const noexcept
{
return map;
}
//! \return `map` with `std::reference_wrapper` removed.
map_type& get_map() noexcept
{
return map;
}
};
//! Usage: `wire::field("foo", wire::as_object(self.foo, to_string{}, wire::as_array))`. Consider `std::ref`.
template<typename T, typename F = identity_, typename G = identity_>
inline constexpr as_object_<T, F, G> as_object(T map, F key_filter = F{}, G value_filter = G{})
{
return {std::move(map), std::move(key_filter), std::move(value_filter)};
}
template<typename T>
inline constexpr bool available(const field_<T, true>&) noexcept
{
return true;
}
template<typename T>
inline bool available(const field_<T, false>& elem)
{
return bool(elem.get_value());
}
template<typename T, typename... U>
inline constexpr bool available(const variant_field_<T, true, U...>&) noexcept
{
return true;
}
template<typename T, typename... U>
inline constexpr bool available(const variant_field_<T, false, U...>& elem)
{
return elem != nullptr;
}
// example usage : `wire::sum(std::size_t(wire::available(fields))...)`
inline constexpr int sum() noexcept
{
return 0;
}
template<typename T, typename... U>
inline constexpr T sum(const T head, const U... tail) noexcept
{
return head + sum(tail...);
}
}

73
src/wire/filters.h Normal file
View File

@@ -0,0 +1,73 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cassert>
#include <type_traits>
#include "lmdb/util.h"
// These functions are to be used with `wire::as_object(...)` key filtering
namespace wire
{
//! Callable that returns the value unchanged; default filter for `as_array` and `as_object`.
struct identity_
{
template<typename T>
const T& operator()(const T& value) const noexcept
{
return value;
}
};
constexpr const identity_ identity{};
//! Callable that forwards enum to get_string.
struct enum_as_string_
{
template<typename T>
auto operator()(const T value) const noexcept(noexcept(get_string(value))) -> decltype(get_string(value))
{
return get_string(value);
}
};
constexpr const enum_as_string_ enum_as_string{};
//! Callable that converts C++11 enum class or integer to integer value.
struct as_integer_
{
template<typename T>
lmdb::native_type<T> operator()(const T value) const noexcept
{
using native = lmdb::native_type<T>;
static_assert(!std::is_signed<native>::value, "integer cannot be signed");
return native(value);
}
};
constexpr const as_integer_ as_integer{};
}

68
src/wire/fwd.h Normal file
View File

@@ -0,0 +1,68 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/utility/string_ref.hpp>
#include <type_traits>
#include "common/expect.h" // monero/src
//! Declare an enum to be serialized as an integer
#define WIRE_AS_INTEGER(type_) \
static_assert(std::is_enum<type_>(), "AS_INTEGER only enum types"); \
template<typename R> \
inline void read_bytes(R& source, type_& dest) \
{ \
std::underlying_type<type_>::type temp{}; \
read_bytes(source, temp); \
dest = type_(temp); \
} \
template<typename W> \
inline void write_bytes(W& dest, const type_ source) \
{ \
write_bytes(dest, std::underlying_type<type_>::type(source)); \
}
//! Declare an enum to be serialized as a string (json) or integer (msgpack)
#define WIRE_DECLARE_ENUM(type) \
const char* get_string(type) noexcept; \
expect<type> type ## _from_string(const boost::string_ref) noexcept; \
void read_bytes(::wire::reader&, type&); \
void write_bytes(::wire::writer&, type)
//! Declare a class/struct serialization for all available formats
#define WIRE_DECLARE_OBJECT(type) \
void read_bytes(::wire::reader&, type&); \
void write_bytes(::wire::writer&, const type&)
namespace wire
{
class reader;
struct writer;
}

54
src/wire/json.h Normal file
View File

@@ -0,0 +1,54 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include "wire/json/base.h"
#include "wire/json/error.h"
#include "wire/json/read.h"
#include "wire/json/write.h"
#define WIRE_JSON_DEFINE_ENUM(type, map) \
void read_bytes(::wire::json_reader& source, type& dest) \
{ \
dest = type(source.enumeration(map)); \
} \
void write_bytes(::wire::json_writer& dest, const type source) \
{ \
dest.enumeration(std::size_t(source), map); \
}
#define WIRE_JSON_DEFINE_OBJECT(type, map) \
void read_bytes(::wire::json_reader& source, type& dest) \
{ \
map(source, dest); \
} \
void write_bytes(::wire::json_writer& dest, const type& source) \
{ \
map(dest, source); \
}

View File

@@ -0,0 +1,33 @@
# Copyright (c) 2020, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(monero-lws_wire-json_sources error.cpp read.cpp write.cpp)
set(monero-lws_wire-json_headers base.h error.h fwd.h read.h write.h)
add_library(monero-lws-wire-json ${monero-lws_wire-json_sources} ${monero-lws-wire-json_headers})
target_link_libraries(monero-lws-wire-json monero::libraries monero-lws-wire)

50
src/wire/json/base.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <string>
#include "byte_slice.h"
#include "common/expect.h"
#include "wire/json/fwd.h"
namespace wire
{
struct json
{
using input_type = json_reader;
using output_type = json_writer;
template<typename T>
static expect<T> from_bytes(std::string&& source);
template<typename T>
static epee::byte_slice to_bytes(const T& source);
};
}

108
src/wire/json/error.cpp Normal file
View File

@@ -0,0 +1,108 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "error.h"
namespace wire
{
namespace error
{
const char* get_string(const rapidjson_e value) noexcept
{
switch (rapidjson::ParseErrorCode(value))
{
default:
break;
case rapidjson::kParseErrorNone:
return "No JSON parsing errors";
// from rapidjson
case rapidjson::kParseErrorDocumentEmpty:
return "JSON parser expected non-empty document";
case rapidjson::kParseErrorDocumentRootNotSingular:
return "JSON parser expected one value at root level";
case rapidjson::kParseErrorValueInvalid:
return "JSON parser found invalid value";
case rapidjson::kParseErrorObjectMissName:
return "JSON parser expected name for object field";
case rapidjson::kParseErrorObjectMissColon:
return "JSON parser expected ':' between name and value";
case rapidjson::kParseErrorObjectMissCommaOrCurlyBracket:
return "JSON parser expected ',' or '}'";
case rapidjson::kParseErrorArrayMissCommaOrSquareBracket:
return "JSON parser expected ',' or ']'";
case rapidjson::kParseErrorStringUnicodeEscapeInvalidHex:
return "JSON parser found invalid unicode escape";
case rapidjson::kParseErrorStringUnicodeSurrogateInvalid:
return "JSON parser found invalid unicode surrogate value";
case rapidjson::kParseErrorStringEscapeInvalid:
return "JSON parser found invalid escape sequence in string value";
case rapidjson::kParseErrorStringMissQuotationMark:
return "JSON parser expected '\"'";
case rapidjson::kParseErrorStringInvalidEncoding:
return "JSON parser found invalid encoding";
case rapidjson::kParseErrorNumberTooBig:
return "JSON parser found number value larger than double float precision";
case rapidjson::kParseErrorNumberMissFraction:
return "JSON parser found number missing fractional component";
case rapidjson::kParseErrorNumberMissExponent:
return "JSON parser found number missing exponent";
case rapidjson::kParseErrorTermination:
return "JSON parser was stopped";
case rapidjson::kParseErrorUnspecificSyntaxError:
return "JSON parser found syntax error";
}
return "Unknown JSON parser error";
}
const std::error_category& rapidjson_category() noexcept
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "wire::error::rapidjson_category()";
}
virtual std::string message(int value) const override final
{
return get_string(rapidjson_e(value));
}
};
static const category instance{};
return instance;
}
} // error
} // wire

53
src/wire/json/error.h Normal file
View File

@@ -0,0 +1,53 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <rapidjson/error/error.h>
#include <system_error>
namespace wire
{
namespace error
{
//! Type wrapper to "grab" rapidjson errors
enum class rapidjson_e : int {};
//! \return Static string describing error `value`.
const char* get_string(rapidjson_e value) noexcept;
//! \return Category for rapidjson generated errors.
const std::error_category& rapidjson_category() noexcept;
//! \return Error code with `value` and `rapidjson_category()`.
inline std::error_code make_error_code(rapidjson_e value) noexcept
{
return std::error_code{int(value), rapidjson_category()};
}
}
}

45
src/wire/json/fwd.h Normal file
View File

@@ -0,0 +1,45 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#define WIRE_JSON_DECLARE_ENUM(type) \
const char* get_string(type) noexcept; \
void read_bytes(::wire::json_reader&, type&); \
void write_bytes(:wire::json_writer&, type)
#define WIRE_JSON_DECLARE_OBJECT(type) \
void read_bytes(::wire::json_reader&, type&); \
void write_bytes(::wire::json_writer&, const type&)
namespace wire
{
struct json;
class json_reader;
class json_writer;
}

413
src/wire/json/read.cpp Normal file
View File

@@ -0,0 +1,413 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "read.h"
#include <algorithm>
#include <limits>
#include <rapidjson/memorystream.h>
#include <stdexcept>
#include "common/expect.h" // monero/src
#include "hex.h" // monero/contrib/epee/include
#include "wire/error.h"
#include "wire/json/error.h"
namespace
{
//! Maximum number of bytes to display "near" JSON error.
constexpr const std::size_t snippet_size = 30;
struct json_default_reject : rapidjson::BaseReaderHandler<rapidjson::UTF8<>, json_default_reject>
{
bool Default() const noexcept { return false; }
};
//! \throw std::system_error by converting `code` into a std::error_code
[[noreturn]] void throw_json_error(const epee::span<char> source, const rapidjson::Reader& reader, const wire::error::schema expected)
{
const std::size_t offset = std::min(source.size(), reader.GetErrorOffset());
const std::size_t start = offset;//std::max(snippet_size / 2, offset) - (snippet_size / 2);
const std::size_t end = start + std::min(snippet_size, source.size() - start);
const boost::string_ref text{source.data() + start, end - start};
const rapidjson::ParseErrorCode parse_error = reader.GetParseErrorCode();
switch (parse_error)
{
default:
WIRE_DLOG_THROW(wire::error::rapidjson_e(parse_error), "near \"" << text << '"');
case rapidjson::kParseErrorNone:
case rapidjson::kParseErrorTermination: // the handler returned false
break;
}
WIRE_DLOG_THROW(expected, "near '" << text << '\'');
}
}
namespace wire
{
struct json_reader::rapidjson_sax
{
struct string_contents
{
const char* ptr;
std::size_t length;
};
union
{
bool boolean;
std::intmax_t integer;
std::uintmax_t unsigned_integer;
double number;
string_contents string;
} value;
error::schema expected_;
bool negative;
explicit rapidjson_sax(error::schema expected) noexcept
: expected_(expected), negative(false)
{}
bool Null() const noexcept
{
return expected_ == error::schema::none;
}
bool Bool(bool i) noexcept
{
value.boolean = i;
return expected_ == error::schema::boolean || expected_ == error::schema::none;
}
bool Int(int i) noexcept
{
return Int64(i);
}
bool Uint(unsigned i) noexcept
{
return Uint64(i);
}
bool Int64(std::int64_t i) noexcept
{
negative = true;
switch(expected_)
{
default:
return false;
case error::schema::integer:
value.integer = i;
break;
case error::schema::number:
value.number = i;
break;
case error::schema::none:
break;
}
return true;
}
bool Uint64(std::uint64_t i) noexcept
{
switch (expected_)
{
default:
return false;
case error::schema::integer:
value.unsigned_integer = i;
break;
case error::schema::number:
value.number = i;
break;
case error::schema::none:
break;
}
return true;
}
bool Double(double i) noexcept
{
value.number = i;
return expected_ == error::schema::number || expected_ == error::schema::none;
}
bool RawNumber(const char*, std::size_t, bool) const noexcept
{
return false;
}
bool String(const char* str, std::size_t length, bool) noexcept
{
value.string = {str, length};
return expected_ == error::schema::string || expected_ == error::schema::none;
}
bool Key(const char* str, std::size_t length, bool)
{
return String(str, length, true);
}
bool StartArray() const noexcept { return expected_ == error::schema::none; }
bool EndArray(std::size_t) const noexcept { return expected_ == error::schema::none; }
bool StartObject() const noexcept { return expected_ == error::schema::none; }
bool EndObject(std::size_t) const noexcept { return expected_ == error::schema::none; }
};
void json_reader::read_next_value(rapidjson_sax& handler)
{
rapidjson::InsituStringStream stream{current_.data()};
if (!reader_.Parse<rapidjson::kParseStopWhenDoneFlag>(stream, handler))
throw_json_error(current_, reader_, handler.expected_);
current_.remove_prefix(stream.Tell());
}
char json_reader::get_next_token()
{
rapidjson::InsituStringStream stream{current_.data()};
rapidjson::SkipWhitespace(stream);
current_.remove_prefix(stream.Tell());
return stream.Peek();
}
boost::string_ref json_reader::get_next_string()
{
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::schema::string);
current_.remove_prefix(1);
void const* const end = std::memchr(current_.data(), '"', current_.size());
if (!end)
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
char const* const begin = current_.data();
const std::size_t length = current_.remove_prefix(static_cast<const char*>(end) - current_.data() + 1);
return {begin, length - 1};
}
void json_reader::skip_value()
{
rapidjson_sax accept_all{error::schema::none};
read_next_value(accept_all);
}
json_reader::json_reader(std::string&& source)
: reader(),
source_(std::move(source)),
current_(std::addressof(source_[0]), source_.size()),
reader_()
{}
void json_reader::check_complete() const
{
if (depth())
WIRE_DLOG_THROW(error::rapidjson_e(rapidjson::kParseErrorUnspecificSyntaxError), "Unexpected end");
}
bool json_reader::boolean()
{
rapidjson_sax json_bool{error::schema::boolean};
read_next_value(json_bool);
return json_bool.value.boolean;
}
std::intmax_t json_reader::integer()
{
rapidjson_sax json_int{error::schema::integer};
read_next_value(json_int);
if (json_int.negative)
return json_int.value.integer;
return integer::convert_to<std::intmax_t>(json_int.value.unsigned_integer);
}
std::uintmax_t json_reader::unsigned_integer()
{
rapidjson_sax json_uint{error::schema::integer};
read_next_value(json_uint);
if (!json_uint.negative)
return json_uint.value.unsigned_integer;
return integer::convert_to<std::uintmax_t>(json_uint.value.integer);
}
/*
const std::vector<std::uintmax_t>& json_reader::unsigned_integer_array()
{
read_next_unsigned_array(
}*/
std::uintmax_t json_reader::safe_unsigned_integer()
{
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::schema::string);
current_.remove_prefix(1);
const std::uintmax_t out = unsigned_integer();
if (get_next_token() != '"')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorStringMissQuotationMark));
current_.remove_prefix(1);
return out;
}
double json_reader::real()
{
rapidjson_sax json_number{error::schema::number};
read_next_value(json_number);
return json_number.value.number;
}
std::string json_reader::string()
{
rapidjson_sax json_string{error::schema::string};
read_next_value(json_string);
return std::string{json_string.value.string.ptr, json_string.value.string.length};
}
std::vector<std::uint8_t> json_reader::binary()
{
const boost::string_ref value = get_next_string();
std::vector<std::uint8_t> out;
out.resize(value.size() / 2);
if (!epee::from_hex::to_buffer(epee::to_mut_span(out), value))
WIRE_DLOG_THROW_(error::schema::binary);
return out;
}
void json_reader::binary(epee::span<std::uint8_t> dest)
{
const boost::string_ref value = get_next_string();
if (!epee::from_hex::to_buffer(dest, value))
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size" << dest.size() * 2 << " but got " << value.size());
}
std::size_t json_reader::enumeration(epee::span<char const* const> enums)
{
rapidjson_sax json_enum{error::schema::string};
read_next_value(json_enum);
const boost::string_ref value{json_enum.value.string.ptr, json_enum.value.string.length};
for (std::size_t i = 0; i < enums.size(); ++i)
{
if (value == enums[i])
return i;
}
WIRE_DLOG_THROW(error::schema::enumeration, value << " is not a valid enum");
return enums.size();
}
std::size_t json_reader::start_array()
{
if (get_next_token() != '[')
WIRE_DLOG_THROW_(error::schema::array);
current_.remove_prefix(1);
increment_depth();
return 0;
}
bool json_reader::is_array_end(const std::size_t count)
{
const char next = get_next_token();
if (next == 0)
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorArrayMissCommaOrSquareBracket));
if (next == ']')
{
current_.remove_prefix(1);
return true;
}
if (count)
{
if (next != ',')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorArrayMissCommaOrSquareBracket));
current_.remove_prefix(1);
}
return false;
}
std::size_t json_reader::start_object()
{
if (get_next_token() != '{')
WIRE_DLOG_THROW_(error::schema::object);
current_.remove_prefix(1);
increment_depth();
return 0;
}
bool json_reader::key(const epee::span<const key_map> map, std::size_t& state, std::size_t& index)
{
rapidjson_sax json_key{error::schema::string};
const auto process_key = [map] (const rapidjson_sax::string_contents value)
{
const boost::string_ref key{value.ptr, value.length};
for (std::size_t i = 0; i < map.size(); ++i)
{
if (map[i].name == key)
return i;
}
return map.size();
};
index = map.size();
for (;;)
{
// check for object or text end
const char next = get_next_token();
if (next == 0)
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissCommaOrCurlyBracket));
if (next == '}')
{
current_.remove_prefix(1);
return false;
}
// parse next field token
if (state)
{
if (next != ',')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissCommaOrCurlyBracket));
current_.remove_prefix(1);
}
++state;
// parse key
read_next_value(json_key);
index = process_key(json_key.value.string);
if (get_next_token() != ':')
WIRE_DLOG_THROW_(error::rapidjson_e(rapidjson::kParseErrorObjectMissColon));
current_.remove_prefix(1);
// parse value
if (index != map.size())
break;
skip_value();
}
return true;
}
}

134
src/wire/json/read.h Normal file
View File

@@ -0,0 +1,134 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/utility/string_ref.hpp>
#include <cstddef>
#include <rapidjson/reader.h>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "wire/field.h"
#include "wire/json/base.h"
#include "wire/read.h"
#include "wire/traits.h"
namespace wire
{
//! Reads JSON tokens one-at-a-time for DOMless parsing
class json_reader : public reader
{
struct rapidjson_sax;
std::string source_;
epee::span<char> current_;
rapidjson::Reader reader_;
void read_next_value(rapidjson_sax& handler);
char get_next_token();
boost::string_ref get_next_string();
//! Skips next value. \throw wire::exception if invalid JSON syntax.
void skip_value();
public:
explicit json_reader(std::string&& source);
//! \throw wire::exception if JSON parsing is incomplete.
void check_complete() const override final;
//! \throw wire::exception if next token not a boolean.
bool boolean() override final;
//! \throw wire::expception if next token not an integer.
std::intmax_t integer() override final;
//! \throw wire::exception if next token not an unsigned integer.
std::uintmax_t unsigned_integer() override final;
//! \throw wire::exception if next token is not an integer encoded as string
std::uintmax_t safe_unsigned_integer();
//! \throw wire::exception if next token not a valid real number
double real() override final;
//! \throw wire::exception if next token not a string
std::string string() override final;
//! \throw wire::exception if next token cannot be read as hex
std::vector<std::uint8_t> binary() override final;
//! \throw wire::exception if next token cannot be read as hex into `dest`.
void binary(epee::span<std::uint8_t> dest) override final;
//! \throw wire::exception if invalid next token invalid enum. \return Index in `enums`.
std::size_t enumeration(epee::span<char const* const> enums) override final;
//! \throw wire::exception if next token not `[`.
std::size_t start_array() override final;
//! Skips whitespace to next token. \return True if next token is eof or ']'.
bool is_array_end(std::size_t count) override final;
//! \throw wire::exception if next token not `{`.
std::size_t start_object() override final;
/*! \throw wire::exception if next token not key or `}`.
\param[out] index of key match within `map`.
\return True if another value to read. */
bool key(epee::span<const key_map> map, std::size_t&, std::size_t& index) override final;
};
// Don't call `read` directly in this namespace, do it from `wire_read`.
template<typename T>
expect<T> json::from_bytes(std::string&& bytes)
{
json_reader source{std::move(bytes)};
return wire_read::to<T>(source);
}
// specialization prevents type "downgrading" to base type in cpp files
template<typename T>
inline void array(json_reader& source, T& dest)
{
wire_read::array(source, dest);
}
template<typename... T>
inline void object(json_reader& source, T... fields)
{
wire_read::object(source, wire_read::tracker<T>{std::move(fields)}...);
}
} // wire

167
src/wire/json/write.cpp Normal file
View File

@@ -0,0 +1,167 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "write.h"
#include <stdexcept>
#include "hex.h" // monero/contrib/epee/include
namespace
{
constexpr const unsigned flush_threshold = 100;
}
namespace wire
{
void json_writer::do_flush(epee::span<const std::uint8_t>)
{}
void json_writer::check_flush()
{
if (needs_flush_ && (bytes_.increase_size() < flush_threshold || bytes_.increase_size() - flush_threshold < bytes_.size()))
flush();
}
void json_writer::check_complete()
{
if (!formatter_.IsComplete())
throw std::logic_error{"json_writer::take_json() failed with incomplete JSON tree"};
}
epee::byte_slice json_writer::take_json()
{
check_complete();
epee::byte_slice out{std::move(bytes_)};
formatter_.Reset(bytes_);
return out;
}
json_writer::~json_writer() noexcept
{}
std::array<char, uint_to_string_size> json_writer::to_string(const std::uintmax_t value) noexcept
{
static_assert(std::numeric_limits<std::uintmax_t>::max() <= std::numeric_limits<std::uint64_t>::max(), "bad uint conversion");
std::array<char, uint_to_string_size> buf{{}};
rapidjson::internal::u64toa(std::uint64_t(value), buf.data());
return buf;
}
void json_writer::integer(const int source)
{
formatter_.Int(source);
check_flush();
}
void json_writer::integer(const std::intmax_t source)
{
static_assert(std::numeric_limits<std::int64_t>::min() <= std::numeric_limits<std::intmax_t>::min(), "too small");
static_assert(std::numeric_limits<std::intmax_t>::max() <= std::numeric_limits<std::int64_t>::max(), "too large");
formatter_.Int64(source);
check_flush();
}
void json_writer::unsigned_integer(const unsigned source)
{
formatter_.Uint(source);
check_flush();
}
void json_writer::unsigned_integer(const std::uintmax_t source)
{
static_assert(std::numeric_limits<std::uintmax_t>::max() <= std::numeric_limits<std::uint64_t>::max(), "too large");
formatter_.Uint64(source);
check_flush();
}
void json_writer::real(const double source)
{
formatter_.Double(source);
check_flush();
}
void json_writer::string(const boost::string_ref source)
{
formatter_.String(source.data(), source.size());
check_flush();
}
void json_writer::binary(epee::span<const std::uint8_t> source)
{/* TODO update monero project
std::array<char, 256> buffer;
if (source.size() <= buffer.size() / 2)
{
if (!epee::to_hex::buffer({buffer.data(), source.size() * 2}, source))
throw std::logic_error{"Invalid buffer size for binary->hex conversion"};
string({buffer.data(), source.size() * 2});
}
else
{*/
const auto hex = epee::to_hex::string(source);
string(hex);
//}
}
void json_writer::enumeration(const std::size_t index, const epee::span<char const* const> enums)
{
if (enums.size() < index)
throw std::logic_error{"Invalid enum/string value"};
string({enums[index], std::strlen(enums[index])});
}
void json_writer::start_array(std::size_t)
{
formatter_.StartArray();
}
void json_writer::end_array()
{
formatter_.EndArray();
}
void json_writer::start_object(std::size_t)
{
formatter_.StartObject();
}
void json_writer::key(const boost::string_ref str)
{
formatter_.Key(str.data(), str.size());
check_flush();
}
void json_writer::key(const std::uintmax_t id)
{
auto str = json_writer::to_string(id);
key(str.data());
}
void json_writer::key(unsigned, const boost::string_ref str)
{
key(str);
}
void json_writer::end_object()
{
formatter_.EndObject();
}
void json_stream_writer::do_flush(epee::span<const std::uint8_t> bytes)
{
dest.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
}

184
src/wire/json/write.h Normal file
View File

@@ -0,0 +1,184 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <array>
#include <boost/utility/string_ref.hpp>
#include <cstdint>
#include <limits>
#include <rapidjson/writer.h>
#include "byte_stream.h" // monero/contrib/epee/include
#include "span.h" // monero/contrib/epee/include
#include "wire/field.h"
#include "wire/filters.h"
#include "wire/json/base.h"
#include "wire/traits.h"
#include "wire/write.h"
namespace wire
{
constexpr const std::size_t uint_to_string_size =
std::numeric_limits<std::uintmax_t>::digits10 + 2;
//! Writes JSON tokens one-at-a-time for DOMless output.
class json_writer : public writer
{
epee::byte_stream bytes_;
rapidjson::Writer<epee::byte_stream> formatter_;
bool needs_flush_;
//! \return True if buffer needs to be cleared
virtual void do_flush(epee::span<const uint8_t>);
//! Flush written bytes to `do_flush(...)` if configured
void check_flush();
protected:
json_writer(bool needs_flush)
: writer(), bytes_(), formatter_(bytes_), needs_flush_(needs_flush)
{}
//! \throw std::logic_error if incomplete JSON tree
void check_complete();
//! \throw std::logic_error if incomplete JSON tree. \return JSON bytes
epee::byte_slice take_json();
//! Flush bytes in local buffer to `do_flush(...)`
void flush()
{
do_flush({bytes_.data(), bytes_.size()});
bytes_ = epee::byte_stream{}; // TODO create .clear() method in monero project
}
public:
json_writer(const json_writer&) = delete;
virtual ~json_writer() noexcept;
json_writer& operator=(const json_writer&) = delete;
//! \return Null-terminated buffer containing uint as decimal ascii
static std::array<char, uint_to_string_size> to_string(std::uintmax_t) noexcept;
void integer(int) override final;
void integer(std::intmax_t) override final;
void unsigned_integer(unsigned) override final;
void unsigned_integer(std::uintmax_t) override final;
void real(double) override final;
void string(boost::string_ref) override final;
void binary(epee::span<const std::uint8_t> source) override final;
void enumeration(std::size_t index, epee::span<char const* const> enums) override final;
void start_array(std::size_t) override final;
void end_array() override final;
void start_object(std::size_t) override final;
void key(std::uintmax_t) override final;
void key(boost::string_ref) override final;
void key(unsigned, boost::string_ref) override final;
void end_object() override final;
};
//! Buffers entire JSON message in memory
struct json_slice_writer final : json_writer
{
explicit json_slice_writer()
: json_writer(false)
{}
//! \throw std::logic_error if incomplete JSON tree \return JSON bytes
epee::byte_slice take_bytes()
{
return json_writer::take_json();
}
};
//! Periodically flushes JSON data to `std::ostream`
class json_stream_writer final : public json_writer
{
std::ostream& dest;
virtual void do_flush(epee::span<const std::uint8_t>) override final;
public:
explicit json_stream_writer(std::ostream& dest)
: json_writer(true), dest(dest)
{}
//! Flush remaining bytes to stream \throw std::logic_error if incomplete JSON tree
void finish()
{
check_complete();
flush();
}
};
template<typename T>
epee::byte_slice json::to_bytes(const T& source)
{
return wire_write::to_bytes<json_slice_writer>(source);
}
template<typename T, typename F = identity_>
inline void array(json_writer& dest, const T& source, F filter = F{})
{
// works with "lazily" computed ranges
wire_write::array(dest, source, 0, std::move(filter));
}
template<typename T, typename F>
inline void write_bytes(json_writer& dest, as_array_<T, F> source)
{
wire::array(dest, source.get_value(), std::move(source.filter));
}
template<typename T>
inline enable_if<is_array<T>::value> write_bytes(json_writer& dest, const T& source)
{
wire::array(dest, source);
}
template<typename T, typename F = identity_, typename G = identity_>
inline void dynamic_object(json_writer& dest, const T& source, F key_filter = F{}, G value_filter = G{})
{
// works with "lazily" computed ranges
wire_write::dynamic_object(dest, source, 0, std::move(key_filter), std::move(value_filter));
}
template<typename T, typename F, typename G>
inline void write_bytes(json_writer& dest, as_object_<T, F, G> source)
{
wire::dynamic_object(dest, source.get_map(), std::move(source.key_filter), std::move(source.value_filter));
}
template<typename... T>
inline void object(json_writer& dest, T... fields)
{
wire_write::object(dest, std::move(fields)...);
}
}

61
src/wire/read.cpp Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "wire/read.h"
#include <stdexcept>
void wire::reader::increment_depth()
{
if (++depth_ == max_read_depth())
WIRE_DLOG_THROW_(error::schema::maximum_depth);
}
[[noreturn]] void wire::integer::throw_exception(std::intmax_t source, std::intmax_t min)
{
WIRE_DLOG_THROW(error::schema::larger_integer, source << " given when " << min << " is minimum permitted");
}
[[noreturn]] void wire::integer::throw_exception(std::uintmax_t source, 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 : ""));
}

471
src/wire/read.h Normal file
View File

@@ -0,0 +1,471 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
#include <limits>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
#include "common/expect.h" // monero/src
#include "span.h" // monero/contrib/epee/include
#include "wire/error.h"
#include "wire/field.h"
#include "wire/traits.h"
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
protected:
//! \throw wire::exception if max depth is reached
void increment_depth();
void decrement_depth() noexcept { --depth_; }
reader(const reader&) = default;
reader(reader&&) = default;
reader& operator=(const reader&) = default;
reader& operator=(reader&&) = default;
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; }
reader() noexcept
: depth_(0)
{}
virtual ~reader() noexcept
{}
//! \return Number of recursive objects and arrays
std::size_t depth() const noexcept { return depth_; }
//! \throw wire::exception if parsing is incomplete.
virtual void check_complete() const = 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;
// ! \throw wire::exception if next value cannot be read as binary
virtual std::vector<std::uint8_t> binary() = 0;
//! \throw wire::exception if next value cannot be read as binary into `dest`.
virtual void binary(epee::span<std::uint8_t> dest) = 0;
//! \throw wire::exception if next value invalid enum. \return Index in `enums`.
virtual std::size_t enumeration(epee::span<char const* const> enums) = 0;
/*! \throw wire::exception if next value not array
\return Number of values to read before calling `is_array_end()`. */
virtual std::size_t start_array() = 0;
//! \return True if there is another element to read.
virtual bool is_array_end(std::size_t count) = 0;
//! \throw wire::exception if array end delimiter not present.
void end_array() noexcept { decrement_depth(); }
//! \throw wire::exception if not object begin. \return State to be given to `key(...)` function.
virtual std::size_t start_object() = 0;
/*! 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() noexcept { decrement_depth(); }
};
inline void read_bytes(reader& source, bool& dest)
{
dest = source.boolean();
}
inline void read_bytes(reader& source, double& dest)
{
dest = source.real();
}
inline void read_bytes(reader& source, std::string& dest)
{
dest = source.string();
}
template<typename R>
inline void read_bytes(R& source, std::vector<std::uint8_t>& dest)
{
dest = source.binary();
}
template<typename T>
inline enable_if<is_blob<T>::value> read_bytes(reader& source, T& dest)
{
source.binary(epee::as_mut_byte_span(dest));
}
namespace integer
{
[[noreturn]] void throw_exception(std::intmax_t source, std::intmax_t min);
[[noreturn]] void throw_exception(std::uintmax_t source, std::uintmax_t max);
template<typename Target, typename U>
inline Target convert_to(const U source)
{
using common = typename std::common_type<Target, U>::type;
static constexpr const Target target_min = std::numeric_limits<Target>::min();
static constexpr const Target target_max = std::numeric_limits<Target>::max();
/* After optimizations, this is:
* 1 check for unsigned -> unsigned (uint, uint)
* 2 checks for signed -> signed (int, int)
* 2 checks for signed -> unsigned-- (
* 1 check for unsigned -> signed (uint, uint)
Put `WIRE_DLOG_THROW` in cpp to reduce code/ASM duplication. Do not
remove first check, signed values can be implicitly converted to
unsigned in some checks. */
if (!std::numeric_limits<Target>::is_signed && source < 0)
throw_exception(std::intmax_t(source), std::intmax_t(0));
else if (common(source) < common(target_min))
throw_exception(std::intmax_t(source), std::intmax_t(target_min));
else if (common(target_max) < common(source))
throw_exception(std::uintmax_t(source), std::uintmax_t(target_max));
return Target(source);
}
}
inline void read_bytes(reader& source, char& dest)
{
dest = integer::convert_to<char>(source.integer());
}
inline void read_bytes(reader& source, short& dest)
{
dest = integer::convert_to<short>(source.integer());
}
inline void read_bytes(reader& source, int& dest)
{
dest = integer::convert_to<int>(source.integer());
}
inline void read_bytes(reader& source, long& dest)
{
dest = integer::convert_to<long>(source.integer());
}
inline void read_bytes(reader& source, long long& dest)
{
dest = integer::convert_to<long long>(source.integer());
}
inline void read_bytes(reader& source, unsigned char& dest)
{
dest = integer::convert_to<unsigned char>(source.unsigned_integer());
}
inline void read_bytes(reader& source, unsigned short& dest)
{
dest = integer::convert_to<unsigned short>(source.unsigned_integer());
}
inline void read_bytes(reader& source, unsigned& dest)
{
dest = integer::convert_to<unsigned>(source.unsigned_integer());
}
inline void read_bytes(reader& source, unsigned long& dest)
{
dest = integer::convert_to<unsigned long>(source.unsigned_integer());
}
inline void read_bytes(reader& source, unsigned long long& dest)
{
dest = integer::convert_to<unsigned long long>(source.unsigned_integer());
}
} // wire
namespace wire_read
{
/*! Don't add a function called `read_bytes` to this namespace, it will prevent
ADL lookup. ADL 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);
//! \return `T` converted from `source` or error.
template<typename T, typename R>
inline expect<T> to(R& source)
{
try
{
T dest{};
read_bytes(source, dest);
source.check_complete();
return dest;
}
catch (const wire::exception& e)
{
return e.code();
}
}
template<typename R, typename T>
inline void array(R& source, T& dest)
{
using value_type = typename T::value_type;
static_assert(!std::is_same<value_type, char>::value, "read array of 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();
dest.clear();
dest.reserve(count);
bool more = count;
while (more || !source.is_array_end(count))
{
dest.emplace_back();
read_bytes(source, dest.back());
--count;
more &= bool(count);
}
return source.end_array();
}
// `unpack_variant_field` identifies which of the variant types was selected. starts with index-0
template<typename R, typename T>
inline void unpack_variant_field(std::size_t, R&, const T&)
{}
template<typename R, typename T, typename U, typename... X>
inline void unpack_variant_field(const std::size_t index, R& source, T& variant, const wire::option<U>& head, const wire::option<X>&... tail)
{
if (index)
unpack_variant_field(index - 1, source, variant, tail...);
else
{
U dest{};
read_bytes(source, dest);
variant = std::move(dest);
}
}
// `unpack_field` expands `variant_field_`s or reads `field_`s directly
template<typename R, typename T, bool Required, typename... U>
inline void unpack_field(const std::size_t index, R& source, wire::variant_field_<T, Required, U...>& dest)
{
unpack_variant_field(index, source, dest.get_value(), static_cast< const wire::option<U>& >(dest)...);
}
template<typename R, typename T>
inline void unpack_field(std::size_t, R& source, wire::field_<T, true>& dest)
{
read_bytes(source, dest.get_value());
}
template<typename R, typename T>
inline void unpack_field(std::size_t, R& source, wire::field_<T, false>& dest)
{
dest.get_value().emplace();
read_bytes(source, *dest.get_value());
}
// `expand_field_map` writes a single `field_` name or all option names in a `variant_field_` to a table
template<std::size_t N>
inline void expand_field_map(std::size_t, wire::reader::key_map (&)[N])
{}
template<std::size_t N, typename T, typename... U>
inline void expand_field_map(std::size_t index, wire::reader::key_map (&map)[N], const T& head, const U&... tail)
{
map[index].name = head.name;
map[index].id = 0;
expand_field_map(index + 1, map, tail...);
}
template<std::size_t N, typename T, bool Required, typename... U>
inline void expand_field_map(std::size_t index, wire::reader::key_map (&map)[N], const wire::variant_field_<T, Required, U...>& field)
{
expand_field_map(index, map, static_cast< const wire::option<U> & >(field)...);
}
//! 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])
{
our_index_ = index;
expand_field_map(index, map, field_); // expands possible inner options
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(index - our_index_, source, field_);
read_ = true;
return 1 + is_required();
}
};
// `expand_tracker_map` writes all `tracker` types to a table
template<std::size_t N>
inline constexpr std::size_t expand_tracker_map(std::size_t index, const wire::reader::key_map (&)[N])
{
return index;
}
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)
{
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");
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] = {};
expand_tracker_map(0, map, fields...);
std::size_t next = 0;
while (source.key(map, 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()...};
throw_exception(wire::error::schema::missing_key, "", missing);
}
source.end_object();
}
} // wire_read
namespace wire
{
template<typename T>
inline void array(reader& source, T& dest)
{
wire_read::array(source, dest);
}
template<typename R, typename T>
inline enable_if<is_array<T>::value> read_bytes(R& source, T& dest)
{
wire_read::array(source, dest);
}
template<typename... T>
inline void object(reader& source, T... fields)
{
wire_read::object(source, wire_read::tracker<T>{std::move(fields)}...);
}
}

46
src/wire/traits.h Normal file
View File

@@ -0,0 +1,46 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <type_traits>
#include <utility>
namespace wire
{
template<bool C>
using enable_if = typename std::enable_if<C>::type;
template<typename T>
struct is_array : std::false_type
{};
template<typename T>
struct is_blob : std::false_type
{};
}

41
src/wire/vector.h Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <type_traits>
#include <vector>
#include "wire/traits.h"
namespace wire
{
template<typename T>
struct is_array<std::vector<T>>
: std::true_type
{};
}

31
src/wire/write.cpp Normal file
View File

@@ -0,0 +1,31 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "write.h"
wire::writer::~writer() noexcept
{}

224
src/wire/write.h Normal file
View File

@@ -0,0 +1,224 @@
// Copyright (c) 2020, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <array>
#include <boost/utility/string_ref.hpp>
#include <cstdint>
#include <type_traits>
#include "byte_slice.h" // monero/contrib/epee/include
#include "span.h" // monero/contrib/epee/include
#include "wire/field.h"
#include "wire/filters.h"
#include "wire/traits.h"
namespace wire
{
//! Interface for converting C/C++ objects to "wire" (byte) formats.
struct writer
{
writer() = default;
virtual ~writer() noexcept;
virtual void integer(int) = 0;
virtual void integer(std::intmax_t) = 0;
virtual void unsigned_integer(unsigned) = 0;
virtual void unsigned_integer(std::uintmax_t) = 0;
virtual void real(double) = 0;
virtual void string(boost::string_ref) = 0;
virtual void binary(epee::span<const std::uint8_t> bytes) = 0;
virtual void enumeration(std::size_t index, epee::span<char const* const> enums) = 0;
virtual void start_array(std::size_t) = 0;
virtual void end_array() = 0;
virtual void start_object(std::size_t) = 0;
virtual void key(std::uintmax_t) = 0;
virtual void key(boost::string_ref) = 0;
virtual void key(unsigned, boost::string_ref) = 0; //!< Implementation should output fastest key
virtual void end_object() = 0;
protected:
writer(const writer&) = default;
writer(writer&&) = default;
writer& operator=(const writer&) = default;
writer& operator=(writer&&) = default;
};
// leave in header, compiler can de-virtualize when final type is given
inline void write_bytes(writer& dest, const int source)
{
dest.integer(source);
}
inline void write_bytes(writer& dest, const std::intmax_t source)
{
dest.integer(source);
}
inline void write_bytes(writer& dest, const unsigned source)
{
dest.unsigned_integer(source);
}
inline void write_bytes(writer& dest, const std::uintmax_t source)
{
dest.unsigned_integer(source);
}
inline void write_bytes(writer& dest, const double source)
{
dest.real(source);
}
inline void write_bytes(writer& dest, const boost::string_ref source)
{
dest.string(source);
}
template<typename T>
inline enable_if<is_blob<T>::value> write_bytes(writer& dest, const T& source)
{
dest.binary(epee::as_byte_span(source));
}
inline void write_bytes(writer& dest, const epee::span<const std::uint8_t> source)
{
dest.binary(source);
}
}
namespace wire_write
{
/*! Don't add a function called `write_bytes` to this namespace, it will
prevent ADL lookup. ADL lookup delays the function searching until the
template is used instead of when its defined. This allows the unqualified
calls to `write_bytes` in this namespace to "find" user functions that are
declared after these functions. */
template<typename W, typename T>
inline epee::byte_slice to_bytes(const T& value)
{
W dest{};
write_bytes(dest, value);
return dest.take_bytes();
}
template<typename W, typename T, typename F = wire::identity_>
inline void array(W& dest, const T& source, const std::size_t count, F filter = F{})
{
using value_type = typename T::value_type;
static_assert(!std::is_same<value_type, char>::value, "write array of chars as binary");
static_assert(!std::is_same<value_type, std::uint8_t>::value, "write array of unsigned chars as binary");
dest.start_array(count);
for (const auto& elem : source)
write_bytes(dest, filter(elem));
dest.end_array();
}
template<typename W, typename T>
inline bool field(W& dest, const wire::field_<T, true> elem)
{
dest.key(0, elem.name);
write_bytes(dest, elem.get_value());
return true;
}
template<typename W, typename T>
inline bool field(W& dest, const wire::field_<T, false> elem)
{
if (bool(elem.get_value()))
{
dest.key(0, elem.name);
write_bytes(dest, *elem.get_value());
}
return true;
}
template<typename W, typename... T>
inline void object(W& dest, T... fields)
{
dest.start_object(wire::sum(std::size_t(wire::available(fields))...));
const bool dummy[] = {field(dest, std::move(fields))...};
dest.end_object();
}
template<typename W, typename T, typename F, typename G>
inline void dynamic_object(W& dest, const T& values, const std::size_t count, F key_filter, G value_filter)
{
dest.start_object(count);
for (const auto& elem : values)
{
dest.key(key_filter(elem.first));
write_bytes(dest, value_filter(elem.second));
}
dest.end_object();
}
} // wire_write
namespace wire
{
template<typename T, typename F = identity_>
inline void array(writer& dest, const T& source, F filter = F{})
{
wire_write::array(dest, source, source.size(), std::move(filter));
}
template<typename T, typename F>
inline void write_bytes(writer& dest, as_array_<T, F> source)
{
wire::array(dest, source.get_value(), std::move(source.filter));
}
template<typename T>
inline enable_if<is_array<T>::value> write_bytes(writer& dest, const T& source)
{
wire::array(dest, source);
}
template<typename T, typename F = identity_, typename G = identity_>
inline void dynamic_object(writer& dest, const T& source, F key_filter = F{}, G value_filter = G{})
{
wire_write::dynamic_object(dest, source, source.size(), std::move(key_filter), std::move(value_filter));
}
template<typename T, typename F, typename G>
inline void write_bytes(writer& dest, as_object_<T, F, G> source)
{
wire::dynamic_object(dest, source.get_map(), std::move(source.key_filter), std::move(source.value_filter));
}
template<typename... T>
inline void object(writer& dest, T... fields)
{
wire_write::object(dest, std::move(fields)...);
}
}