Adding msgpack support to ::wire:: library (#63)

This commit is contained in:
Lee *!* Clagett 2023-04-05 10:16:50 -04:00 committed by Lee *!* Clagett
parent 64f5d4a9ab
commit 3ad71ba01e
19 changed files with 1876 additions and 50 deletions

View file

@ -34,3 +34,4 @@ target_include_directories(monero-lws-wire PUBLIC "${LMDB_INCLUDE}")
target_link_libraries(monero-lws-wire PRIVATE monero::libraries)
add_subdirectory(json)
add_subdirectory(msgpack)

View file

@ -1,4 +1,4 @@
// Copyright (c) 2020, The Monero Project
// Copyright (c) 2020-2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@ -33,9 +33,13 @@
#include "wire/filters.h"
#include "wire/traits.h"
//! A required field with the same key name and C/C++ name
#define WIRE_FIELD_ID(id, name) \
::wire::field< id >( #name , std::ref( self . name ))
//! A required field has the same key name and C/C++ name
#define WIRE_FIELD(name) \
::wire::field( #name , std::ref( self . name ))
#define WIRE_FIELD(name) \
WIRE_FIELD_ID(0, name)
//! A required field has the same key name and C/C++ name AND is cheap to copy (faster output).
#define WIRE_FIELD_COPY(name) \
@ -61,12 +65,13 @@ namespace wire
//! Links `name` to a `value` for object serialization.
template<typename T, bool Required>
template<typename T, bool Required, unsigned I = 0>
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; }
static constexpr unsigned id() noexcept { return I; }
const char* name;
T value;
@ -85,15 +90,15 @@ namespace wire
};
//! Links `name` to `value`. Use `std::ref` if de-serializing.
template<typename T>
constexpr inline field_<T, true> field(const char* name, T value)
template<unsigned I = 0, typename T = void>
constexpr inline field_<T, true, I> field(const char* name, T value)
{
return {name, std::move(value)};
}
//! Links `name` to `value`. Use `std::ref` if de-serializing.
template<typename T>
constexpr inline field_<T, false> optional_field(const char* name, T value)
template<unsigned I = 0, typename T = void>
constexpr inline field_<T, false, I> optional_field(const char* name, T value)
{
return {name, std::move(value)};
}
@ -103,6 +108,7 @@ namespace wire
template<typename T>
struct option
{
static constexpr unsigned id() noexcept { return 0; }
const char* name;
};
@ -243,13 +249,13 @@ namespace wire
}
template<typename T>
inline constexpr bool available(const field_<T, true>&) noexcept
template<typename T, unsigned I>
inline constexpr bool available(const field_<T, true, I>&) noexcept
{
return true;
}
template<typename T>
inline bool available(const field_<T, false>& elem)
template<typename T, unsigned I>
inline bool available(const field_<T, false, I>& elem)
{
return bool(elem.get_value());
}

54
src/wire/msgpack.h Normal file
View file

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

View file

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

171
src/wire/msgpack/base.h Normal file
View file

@ -0,0 +1,171 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <cstdint>
#include <string>
#include <tuple>
#include "byte_slice.h"
#include "common/expect.h"
#include "wire/msgpack/fwd.h"
namespace wire
{
struct msgpack
{
using input_type = msgpack_reader;
using output_type = msgpack_writer;
//! Tags that do not require bitmask to identify
enum class tag : std::uint8_t
{
nil = 0xc0,
unused,
False,
True,
binary8,
binary16,
binary32,
extension8,
extension16,
extension32,
float32,
float64,
uint8,
uint16,
uint32,
uint64,
int8,
int16,
int32,
int64,
fixed_extension1,
fixed_extension2,
fixed_extension4,
fixed_extension8,
fixed_extension16,
string8,
string16,
string32,
array16,
array32,
object16,
object32
};
//! Link a fixed tag `T` to its corresponding mask `M` and max value `N`
template<std::uint8_t T, std::uint8_t M, std::uint8_t N>
struct fixed_tag
{
static constexpr std::uint8_t tag() noexcept { return T; }
static constexpr std::uint8_t mask() noexcept { return M; }
static constexpr std::uint8_t max() noexcept { return N; }
//! \return True if `value` is fixed tag `T`
static constexpr bool matches(const std::uint8_t value) noexcept
{ return (value & mask()) == tag(); }
//! \return True if `value` is fixed tag `T`
static constexpr bool matches(const msgpack::tag value) noexcept
{ return matches(std::uint8_t(value)); }
//! \return Value encoded in fixed tag
static constexpr std::uint8_t extract(const std::uint8_t value) noexcept
{ return value & ~mask(); }
//! \return Value encoded in fixed tag
static constexpr std::uint8_t extract(const msgpack::tag value) noexcept
{ return extract(std::uint8_t(value)); }
};
// Tags requiring bitmask to identify
using ftag_unsigned = fixed_tag<0x00, 0x80, 0x7f>;
using ftag_signed = fixed_tag<0xe0, 0xe0, 0>;
using ftag_string = fixed_tag<0xa0, 0xe0, 31>;
using ftag_array = fixed_tag<0x90, 0xf0, 15>;
using ftag_object = fixed_tag<0x80, 0xf0, 15>;
//! Link a msgpack tag to a C++ numeric
template<typename T, tag V>
struct type
{
static constexpr bool is_signed() noexcept { return std::numeric_limits<T>::is_signed; }
static constexpr T min() noexcept { return std::numeric_limits<T>::min(); }
static constexpr T max() noexcept { return std::numeric_limits<T>::max(); }
static constexpr tag Tag() noexcept { return V; }
};
using int8 = type<std::int8_t, tag::int8>;
using int16 = type<std::int16_t, tag::int16>;
using int32 = type<std::int32_t, tag::int32>;
using int64 = type<std::int64_t, tag::int64>;
using signed_types = std::tuple<int8, int16, int32, int64>;
using uint8 = type<std::uint8_t, tag::uint8>;
using uint16 = type<std::uint16_t, tag::uint16>;
using uint32 = type<std::uint32_t, tag::uint32>;
using uint64 = type<std::uint64_t, tag::uint64>;
using unsigned_types = std::tuple<uint8, uint16, uint32, uint64>;
using integer_types = std::tuple<
msgpack::uint8, msgpack::int8, msgpack::uint16, msgpack::int16,
msgpack::uint32, msgpack::int32, msgpack::uint64, msgpack::int64
>;
using string8 = type<std::uint8_t, tag::string8>;
using string16 = type<std::uint16_t, tag::string16>;
using string32 = type<std::uint32_t, tag::string32>;
using string_types = std::tuple<string8, string16, string32>;
using binary8 = type<std::uint8_t, tag::binary8>;
using binary16 = type<std::uint16_t, tag::binary16>;
using binary32 = type<std::uint32_t, tag::binary32>;
using binary_types = std::tuple<binary8, binary16, binary32>;
using extension8 = type<std::uint8_t, tag::extension8>;
using extension16 = type<std::uint16_t, tag::extension16>;
using extension32 = type<std::uint32_t, tag::extension32>;
using extension_types = std::tuple<extension8, extension16, extension32>;
using array16 = type<std::uint16_t, tag::array16>;
using array32 = type<std::uint32_t, tag::array32>;
using array_types = std::tuple<array16, array32>;
using object16 = type<std::uint16_t, tag::object16>;
using object32 = type<std::uint32_t, tag::object32>;
using object_types = std::tuple<object16, object32>;
template<typename T>
static expect<T> from_bytes(epee::byte_slice&& source);
template<typename T>
static epee::byte_slice to_bytes(const T& source);
};
}

View file

@ -0,0 +1,71 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "error.h"
namespace wire
{
namespace error
{
const char* get_string(const msgpack value) noexcept
{
switch (value)
{
default:
break;
case msgpack::incomplete:
return "Incomplete msgpack tree structure";
case msgpack::integer_encoding:
return "Unable to encode integer in msgpack";
case msgpack::invalid:
return "Invalid msgpack encoding";
case msgpack::not_enough_bytes:
return "Expected more bytes in the msgpack stream";
}
return "Unknown msgpack error";
}
const std::error_category& msgpack_category() noexcept
{
struct category final : std::error_category
{
virtual const char* name() const noexcept override final
{
return "wire::error::msgpack_category()";
}
virtual std::string message(int value) const override final
{
return get_string(msgpack(value));
}
};
static const category instance{};
return instance;
}
} // error
} // wire

59
src/wire/msgpack/error.h Normal file
View file

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

45
src/wire/msgpack/fwd.h Normal file
View file

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

517
src/wire/msgpack/read.cpp Normal file
View file

@ -0,0 +1,517 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "read.h"
#include <boost/endian/buffers.hpp>
#include <boost/fusion/include/any.hpp>
#include <boost/fusion/include/std_tuple.hpp>
#include <boost/numeric/conversion/cast.hpp>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <type_traits>
#include "wire/error.h"
#include "wire/msgpack/error.h"
// Expands to every possible fixed string tag value
#define MLWS_FIXED_STRING_TAGS() \
case wire::msgpack::tag(0xa0): case wire::msgpack::tag(0xa1): \
case wire::msgpack::tag(0xa2): case wire::msgpack::tag(0xa3): \
case wire::msgpack::tag(0xa4): case wire::msgpack::tag(0xa5): \
case wire::msgpack::tag(0xa6): case wire::msgpack::tag(0xa7): \
case wire::msgpack::tag(0xa8): case wire::msgpack::tag(0xa9): \
case wire::msgpack::tag(0xaa): case wire::msgpack::tag(0xab): \
case wire::msgpack::tag(0xac): case wire::msgpack::tag(0xad): \
case wire::msgpack::tag(0xae): case wire::msgpack::tag(0xaf): \
case wire::msgpack::tag(0xb0): case wire::msgpack::tag(0xb1): \
case wire::msgpack::tag(0xb2): case wire::msgpack::tag(0xb3): \
case wire::msgpack::tag(0xb4): case wire::msgpack::tag(0xb5): \
case wire::msgpack::tag(0xb6): case wire::msgpack::tag(0xb7): \
case wire::msgpack::tag(0xb8): case wire::msgpack::tag(0xb9): \
case wire::msgpack::tag(0xba): case wire::msgpack::tag(0xbb): \
case wire::msgpack::tag(0xbc): case wire::msgpack::tag(0xbd): \
case wire::msgpack::tag(0xbe): case wire::msgpack::tag(0xbf):
namespace
{
template<typename T>
using limits = std::numeric_limits<T>;
//! \return True iif `value` matches a tag in `T` tuple.
template<typename T>
bool matches(const wire::msgpack::tag tag)
{
const auto matched_type = [tag] (const auto type)
{
return type.Tag() == tag;
};
// NOTE: This is slower than a switch but more flexible/reusable
return boost::fusion::any(T{}, matched_type);
}
//! \return Integer `T` encoded as big endian in `source`.
template<typename T>
T read_endian(epee::byte_slice& source)
{
static_assert(std::is_integral<T>::value, "must be integral type");
static constexpr const std::size_t bits = 8 * sizeof(T);
using buffer_type =
boost::endian::endian_buffer<boost::endian::order::big, T, bits>;
buffer_type buffer;
static_assert(sizeof(buffer) == sizeof(T), "unexpected buffer size");
if (source.size() < sizeof(buffer))
WIRE_DLOG_THROW_(wire::error::msgpack::not_enough_bytes);
std::memcpy(std::addressof(buffer), source.data(), sizeof(buffer));
source.remove_prefix(sizeof(buffer));
return buffer.value();
}
//! \return Integer `T` encoded as big endian in `source`.
template<typename T, wire::msgpack::tag U>
T read_endian(epee::byte_slice& source, const wire::msgpack::type<T, U>)
{ return read_endian<T>(source); }
//! \return Integer `T` whose encoding is specified by tag `next`
template<typename T>
T read_integer(epee::byte_slice& source, const wire::msgpack::tag next)
{
try
{
// msgpack::integer_types
switch (next)
{
default:
break;
case wire::msgpack::tag::int8:
return boost::numeric_cast<T>(read_endian<std::int8_t>(source));
case wire::msgpack::tag::uint8:
return boost::numeric_cast<T>(read_endian<std::uint8_t>(source));
case wire::msgpack::tag::int16:
return boost::numeric_cast<T>(read_endian<std::int16_t>(source));
case wire::msgpack::tag::uint16:
return boost::numeric_cast<T>(read_endian<std::uint16_t>(source));
case wire::msgpack::tag::int32:
return boost::numeric_cast<T>(read_endian<std::int32_t>(source));
case wire::msgpack::tag::uint32:
return boost::numeric_cast<T>(read_endian<std::uint32_t>(source));
case wire::msgpack::tag::int64:
return boost::numeric_cast<T>(read_endian<std::int64_t>(source));
case wire::msgpack::tag::uint64:
return boost::numeric_cast<T>(read_endian<std::uint64_t>(source));
}
}
catch (const boost::numeric::positive_overflow&)
{ WIRE_DLOG_THROW_(wire::error::schema::smaller_integer); }
catch (const boost::numeric::negative_overflow&)
{ WIRE_DLOG_THROW_(wire::error::schema::larger_integer); }
WIRE_DLOG_THROW_(wire::error::schema::integer);
}
epee::byte_slice read_raw(epee::byte_slice& source, const std::size_t bytes)
{
if (source.size() < bytes)
WIRE_DLOG_THROW_(wire::error::msgpack::not_enough_bytes);
return source.take_slice(bytes);
}
template<typename T>
epee::byte_slice read_raw(epee::byte_slice& source)
{
return read_raw(source, wire::integer::cast_unsigned<std::size_t>(read_endian<T>(source)));
}
epee::byte_slice read_string(epee::byte_slice& source, const wire::msgpack::tag next)
{
switch (next)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
MLWS_FIXED_STRING_TAGS()
return read_raw(source, wire::msgpack::ftag_string::extract(next));
#pragma GCC diagnostic pop
case wire::msgpack::tag::string8:
return read_raw<std::uint8_t>(source);
case wire::msgpack::tag::string16:
return read_raw<std::uint16_t>(source);
case wire::msgpack::tag::string32:
return read_raw<std::uint32_t>(source);
default:
break;
}
WIRE_DLOG_THROW_(wire::error::schema::string);
}
//! \return Binary blob encoded message
epee::byte_slice read_binary(epee::byte_slice& source, const wire::msgpack::tag next)
{
switch (next)
{
case wire::msgpack::tag::binary8:
return read_raw<std::uint8_t>(source);
case wire::msgpack::tag::binary16:
return read_raw<std::uint16_t>(source);
case wire::msgpack::tag::binary32:
return read_raw<std::uint32_t>(source);
default:
break;
}
WIRE_DLOG_THROW_(wire::error::schema::string);
}
}
namespace wire
{
void msgpack_reader::throw_logic_error()
{
throw std::logic_error{"Bug in msgpack_reader usage"};
}
void msgpack_reader::skip_value()
{
assert(remaining_);
if (limits<std::size_t>::max() == remaining_)
throw std::runtime_error{"msgpack_reader exceeded tree tracking"};
const std::size_t initial = remaining_;
do
{
const std::size_t size = source_.size();
const msgpack::tag next = peek_tag();
switch (next)
{
default:
break;
case msgpack::tag::nil:
case msgpack::tag::unused:
case msgpack::tag::False:
case msgpack::tag::True:
source_.remove_prefix(1);
break;
case msgpack::tag::binary8:
case msgpack::tag::binary16:
case msgpack::tag::binary32:
source_.remove_prefix(1);
read_binary(source_, next);
break;
case msgpack::tag::extension8:
source_.remove_prefix(1);
read_raw<std::uint8_t>(source_);
source_.remove_prefix(1);
break;
case msgpack::tag::extension16:
source_.remove_prefix(1);
read_raw<std::uint16_t>(source_);
source_.remove_prefix(1);
break;
case msgpack::tag::extension32:
source_.remove_prefix(1);
read_raw<std::uint32_t>(source_);
source_.remove_prefix(1);
break;
case msgpack::tag::int8:
case msgpack::tag::uint8:
source_.remove_prefix(2);
break;
case msgpack::tag::int16:
case msgpack::tag::uint16:
case msgpack::tag::fixed_extension1:
source_.remove_prefix(3);
break;
case msgpack::tag::int32:
case msgpack::tag::uint32:
case msgpack::tag::float32:
source_.remove_prefix(5);
break;
case msgpack::tag::int64:
case msgpack::tag::uint64:
case msgpack::tag::float64:
source_.remove_prefix(9);
break;
case msgpack::tag::fixed_extension2:
source_.remove_prefix(4);
break;
case msgpack::tag::fixed_extension4:
source_.remove_prefix(6);
break;
case msgpack::tag::fixed_extension8:
source_.remove_prefix(10);
break;
case msgpack::tag::fixed_extension16:
source_.remove_prefix(18);
break;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch"
MLWS_FIXED_STRING_TAGS()
case msgpack::tag::string8:
case msgpack::tag::string16:
case msgpack::tag::string32:
source_.remove_prefix(1);
read_string(source_, next);
break;
case msgpack::tag(0x90): case msgpack::tag(0x91): case msgpack::tag(0x92):
case msgpack::tag(0x93): case msgpack::tag(0x94): case msgpack::tag(0x95):
case msgpack::tag(0x96): case msgpack::tag(0x97): case msgpack::tag(0x98):
case msgpack::tag(0x99): case msgpack::tag(0x9a): case msgpack::tag(0x9b):
case msgpack::tag(0x9c): case msgpack::tag(0x9d): case msgpack::tag(0x9e):
case msgpack::tag(0x9f):
case msgpack::tag::array16:
case msgpack::tag::array32:
start_array();
break;
case msgpack::tag(0x80): case msgpack::tag(0x81): case msgpack::tag(0x82):
case msgpack::tag(0x83): case msgpack::tag(0x84): case msgpack::tag(0x85):
case msgpack::tag(0x86): case msgpack::tag(0x87): case msgpack::tag(0x88):
case msgpack::tag(0x89): case msgpack::tag(0x8a): case msgpack::tag(0x8b):
case msgpack::tag(0x8c): case msgpack::tag(0x8d): case msgpack::tag(0x8e):
case msgpack::tag(0x8f):
case msgpack::tag::object16:
case msgpack::tag::object32:
start_object();
break;
#pragma GCC diagnostic pop
};
if (size == source_.size())
{
if (!msgpack::ftag_unsigned::matches(next) && !msgpack::ftag_signed::matches(next))
WIRE_DLOG_THROW_(error::msgpack::invalid);
source_.remove_prefix(1);
}
update_remaining();
} while (initial <= remaining_);
}
msgpack::tag msgpack_reader::peek_tag()
{
if (source_.empty())
WIRE_DLOG_THROW_(error::msgpack::not_enough_bytes);
return msgpack::tag(*source_.data());
}
msgpack::tag msgpack_reader::get_tag()
{
const msgpack::tag next = peek_tag();
source_.remove_prefix(1);
return next;
}
std::intmax_t msgpack_reader::do_integer(const msgpack::tag next)
{
if (msgpack::ftag_signed::matches(next))
return *reinterpret_cast<const std::int8_t*>(std::addressof(next)); // special case
return read_integer<std::intmax_t>(source_, next);
}
std::uintmax_t msgpack_reader::do_unsigned_integer(const msgpack::tag next)
{
return read_integer<std::uintmax_t>(source_, next);
}
template<typename T, typename U>
std::size_t msgpack_reader::read_count(const error::schema expected)
{
const msgpack::tag next = get_tag();
if (T::matches(next))
return T::extract(next);
std::size_t out = 0;
const auto matched_type = [this, &out, next](const auto type)
{
if (type.Tag() == next)
{
out = integer::cast_unsigned<std::size_t>(read_endian(source_, type));
return true;
}
return false;
};
if (!boost::fusion::any(U{}, matched_type))
WIRE_DLOG_THROW_(expected);
return out;
}
void msgpack_reader::check_complete() const
{
if (remaining_)
WIRE_DLOG_THROW_(error::msgpack::incomplete);
}
bool msgpack_reader::boolean()
{
update_remaining();
switch (get_tag())
{
case msgpack::tag::True:
return true;
case msgpack::tag::False:
return false;
default:
break;
}
WIRE_DLOG_THROW_(error::schema::boolean);
}
double msgpack_reader::real()
{
update_remaining();
const auto read_float = [this](auto value)
{
if (source_.size() < sizeof(value))
WIRE_DLOG_THROW_(error::msgpack::not_enough_bytes);
std::memcpy(std::addressof(value), source_.data(), sizeof(value));
source_.remove_prefix(sizeof(value));
return value;
};
switch (get_tag())
{
case msgpack::tag::float32:
return read_float(float(0));
case msgpack::tag::float64:
return read_float(double(0));
default:
break;
}
WIRE_DLOG_THROW_(error::schema::number);
}
std::string msgpack_reader::string()
{
update_remaining();
const epee::byte_slice bytes = read_string(source_, get_tag());
return std::string{reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}
std::vector<std::uint8_t> msgpack_reader::binary()
{
update_remaining();
const epee::byte_slice bytes = read_binary(source_, get_tag());
return std::vector<std::uint8_t>{bytes.begin(), bytes.end()};
}
void msgpack_reader::binary(epee::span<std::uint8_t> dest)
{
update_remaining();
const epee::byte_slice bytes = read_binary(source_, get_tag());
if (dest.size() != bytes.size())
WIRE_DLOG_THROW(error::schema::fixed_binary, "of size " << dest.size() << " but got " << bytes.size());
std::memcpy(dest.data(), bytes.data(), dest.size());
}
std::size_t msgpack_reader::enumeration(const epee::span<char const* const> enums)
{
const std::uintmax_t value = unsigned_integer();
if (enums.size() < value)
WIRE_DLOG_THROW(error::schema::enumeration, value << " is not a valid enum");
return std::size_t(value);
}
std::size_t msgpack_reader::start_array()
{
const std::size_t upcoming =
read_count<msgpack::ftag_array, msgpack::array_types>(error::schema::array);
if (limits<std::size_t>::max() - remaining_ < upcoming)
throw std::runtime_error{"Exceeded max tree tracking for msgpack_reader"};
remaining_ += upcoming;
return upcoming;
}
bool msgpack_reader::is_array_end(const std::size_t count)
{
if (count)
return false;
update_remaining();
return true;
}
std::size_t msgpack_reader::start_object()
{
const std::size_t upcoming =
read_count<msgpack::ftag_object, msgpack::object_types>(error::schema::object);
if (limits<std::size_t>::max() / 2 < upcoming)
throw std::runtime_error{"Exceeded max object tracking for msgpack_reader"};
if (limits<std::size_t>::max() - remaining_ < upcoming * 2)
throw std::runtime_error{"Exceeded msgpack_reader:: tree tracking"};
remaining_ += upcoming * 2;
return upcoming;
}
bool msgpack_reader::key(const epee::span<const key_map> map, std::size_t& state, std::size_t& index)
{
index = map.size();
for ( ;state; --state)
{
update_remaining(); // for key
const msgpack::tag next = get_tag();
const bool single = msgpack::ftag_unsigned::matches(next);
if (single || matches<msgpack::unsigned_types>(next))
{
unsigned key = std::uint8_t(next);
if (!single)
key = read_integer<unsigned>(source_, next);
for (const key_map& elem : map)
{
if (elem.id == key)
{
index = std::addressof(elem) - map.begin();
break;
}
}
}
else if (msgpack::ftag_string::matches(next) || matches<msgpack::string_types>(next))
{
const epee::byte_slice key = read_string(source_, next);
for (const key_map& elem : map)
{
const boost::string_ref elem_{elem.name};
if (key.size() == elem_.size() && std::memcmp(key.data(), elem_.data(), key.size()) == 0)
{
index = std::addressof(elem) - map.begin();
break;
}
}
}
else
WIRE_DLOG_THROW(error::schema::invalid_key, "Invalid key type");
if (index < map.size())
{
--state;
return true;
}
skip_value();
} // until state == 0
update_remaining(); // for end of object
return false;
}
}

164
src/wire/msgpack/read.h Normal file
View file

@ -0,0 +1,164 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <boost/utility/string_ref.hpp>
#include <cstddef>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "wire/field.h"
#include "wire/msgpack/base.h"
#include "wire/read.h"
#include "wire/traits.h"
namespace wire
{
//! Reads MSGPACK tokens one-at-a-time for DOMless parsing
class msgpack_reader : public reader
{
epee::byte_slice source_;
std::size_t remaining_; //!< Expected number of elements remaining
//! \throw std::logic_error
[[noreturn]] void throw_logic_error();
//! Decrement remaining_ if not zero, \throw std::logic_error when `remaining_ == 0`.
void update_remaining()
{
if (remaining_)
--remaining_;
else
throw_logic_error();
}
//! Skips next value. \throw wire::exception if invalid JSON syntax.
void skip_value();
//! \return Next tag but leave `source_` untouched.
msgpack::tag peek_tag();
//! \return Next tag and remove first byte from `source_`.
msgpack::tag get_tag();
//! \return Integer from `soure_` where positive fixed tag has been checked.
std::intmax_t do_integer(msgpack::tag);
//! \return Integer from `source_` where fixed tag has been checked.
std::uintmax_t do_unsigned_integer(msgpack::tag);
//! \return Number of items determined by `T` fixed tag and `U` tuple of tags.
template<typename T, typename U>
std::size_t read_count(error::schema);
public:
explicit msgpack_reader(epee::byte_slice&& source)
: reader(), source_(std::move(source)), remaining_(1)
{}
//! \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
{
update_remaining();
const msgpack::tag next = get_tag();
if (std::uint8_t(next) <= msgpack::ftag_unsigned::max())
return std::uint8_t(next);
return do_integer(next);
}
//! \throw wire::exception if next token not an unsigned integer.
std::uintmax_t unsigned_integer() override final
{
update_remaining();
const msgpack::tag next = get_tag();
if (std::uint8_t(next) <= msgpack::ftag_unsigned::max())
return std::uint8_t(next);
return do_unsigned_integer(next);
}
//! \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;
//! \return true when `count == 0`.
bool is_array_end(const 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> msgpack::from_bytes(epee::byte_slice&& bytes)
{
msgpack_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(msgpack_reader& source, T& dest)
{
wire_read::array(source, dest);
}
template<typename... T>
inline void object(msgpack_reader& source, T... fields)
{
wire_read::object(source, wire_read::tracker<T>{std::move(fields)}...);
}
} // wire

219
src/wire/msgpack/write.cpp Normal file
View file

@ -0,0 +1,219 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "write.h"
#include <boost/endian/buffers.hpp>
#include <boost/fusion/include/any.hpp>
#include <boost/fusion/include/std_tuple.hpp>
#include <cassert>
#include <limits>
#include <ostream>
#include <stdexcept>
#include <type_traits>
#include "wire/error.h"
#include "wire/msgpack/error.h"
namespace
{
template<typename T>
using limits = std::numeric_limits<T>;
constexpr const unsigned flush_threshold = 100;
constexpr const unsigned max_buffer = 4096;
void write_tag(epee::byte_stream& bytes, const wire::msgpack::tag value)
{
bytes.put(std::uint8_t(value));
}
template<typename T, typename U, wire::msgpack::tag tag>
void write_endian(epee::byte_stream& bytes, const T value, const wire::msgpack::type<U, tag> type)
{
static_assert(std::is_integral<T>::value, "input not integral");
static_assert(std::is_integral<U>::value, "output not integral");
using in_limits = std::numeric_limits<T>;
using out_limits = std::numeric_limits<U>;
static_assert(in_limits::is_signed == out_limits::is_signed, "signs must match");
assert(type.min() <= value);
assert(value <= type.max());
static constexpr const std::size_t bits = 8 * sizeof(U);
using buffer_type =
boost::endian::endian_buffer<boost::endian::order::big, U, bits>;
buffer_type buffer(value);
write_tag(bytes, type.Tag());
bytes.write(buffer.data(), sizeof(buffer));
}
template<typename T>
void write_count(epee::byte_stream& bytes, const std::uintmax_t items)
{
const auto match_size = [&bytes, items] (const auto type) -> bool
{
if (type.max() < items)
return false;
write_endian(bytes, items, type);
return true;
};
if (!boost::fusion::any(T{}, match_size))
WIRE_DLOG_THROW_(wire::error::msgpack::integer_encoding);
}
template<typename T, typename U>
void write_count(epee::byte_stream& bytes, const std::uintmax_t items)
{
if (items <= T::max())
bytes.put(T::tag() | std::uint8_t(items));
else
write_count<U>(bytes, items);
}
}
namespace wire
{
void msgpack_writer::do_flush(epee::span<const std::uint8_t>)
{}
void msgpack_writer::check_flush()
{
if (needs_flush_ && (max_buffer < bytes_.size() || bytes_.available() < flush_threshold))
flush();
}
void msgpack_writer::do_integer(const std::intmax_t value)
{
assert(value < 0); // constraint checked in header
if (0xe0 < value) // 0xe0 needs to be type `int` to work
{
bytes_.put(std::uint8_t(value));
return;
}
const auto match_size = [this, value] (const auto type) -> bool
{
if (value < type.min())
return false;
write_endian(bytes_, value, type);
return true;
};
if (!boost::fusion::any(wire::msgpack::signed_types{}, match_size))
WIRE_DLOG_THROW_(wire::error::msgpack::integer_encoding);
}
void msgpack_writer::do_unsigned_integer(const std::uintmax_t value)
{
const auto match_size = [this, value] (const auto type) -> bool
{
if (type.max() < value)
return false;
write_endian(bytes_, value, type);
return true;
};
if (!boost::fusion::any(wire::msgpack::unsigned_types{}, match_size))
WIRE_DLOG_THROW_(wire::error::msgpack::integer_encoding);
}
void msgpack_writer::check_complete()
{
if (expected_)
throw std::logic_error{"msgpack_writer::take_msgpack() failed with incomplete tree"};
}
epee::byte_slice msgpack_writer::take_msgpack()
{
check_complete();
epee::byte_slice out{std::move(bytes_)};
bytes_.clear();
return out;
}
msgpack_writer::~msgpack_writer() noexcept
{}
void msgpack_writer::real(const double source)
{
write_tag(bytes_, msgpack::tag::float64);
bytes_.write(reinterpret_cast<const char*>(std::addressof(source)), sizeof(source));
--expected_;
}
void msgpack_writer::string(const boost::string_ref source)
{
write_count<msgpack::ftag_string, msgpack::string_types>(bytes_, source.size());
bytes_.write(source.data(), source.size());
--expected_;
}
void msgpack_writer::binary(epee::span<const std::uint8_t> source)
{
write_count<msgpack::binary_types>(bytes_, source.size());
bytes_.write(source);
--expected_;
}
void msgpack_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"};
unsigned_integer(index);
}
void msgpack_writer::start_array(const std::size_t items)
{
write_count<msgpack::ftag_array, msgpack::array_types>(bytes_, items);
expected_ += items;
}
void msgpack_writer::start_object(const std::size_t items)
{
write_count<msgpack::ftag_object, msgpack::object_types>(bytes_, items);
expected_ += items * 2;
}
void msgpack_writer::key(const boost::string_ref str)
{
string(str);
}
void msgpack_writer::key(const std::uintmax_t id)
{
unsigned_integer(id);
}
void msgpack_writer::key(const unsigned id, boost::string_ref str)
{
if (integer_keys_)
key(id);
else
key(str);
}
void msgpack_stream_writer::do_flush(epee::span<const std::uint8_t> bytes)
{
dest.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
}
}

225
src/wire/msgpack/write.h Normal file
View file

@ -0,0 +1,225 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <array>
#include <boost/utility/string_ref.hpp>
#include <cstdint>
#include <iosfwd>
#include <limits>
#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/msgpack/base.h"
#include "wire/traits.h"
#include "wire/write.h"
namespace wire
{
//! Writes MSGPACK tokens one-at-a-time for DOMless output.
class msgpack_writer : public writer
{
epee::byte_stream bytes_;
std::size_t expected_; //! Tracks number of expected elements remaining
const bool integer_keys_;
bool needs_flush_;
//! Provided data currently in `bytes_`.
virtual void do_flush(epee::span<const uint8_t>);
//! Flush written bytes to `do_flush(...)` if configured
void check_flush();
void do_integer(std::intmax_t);
void do_unsigned_integer(std::uintmax_t);
template<typename T>
void integer_t(const T value)
{
if (0 <= value)
{
if (value <= msgpack::ftag_unsigned::max())
bytes_.put(std::uint8_t(value));
else // if multibyte
do_unsigned_integer(std::uintmax_t(value));
}
else // if negative
do_integer(value);
--expected_;
}
template<typename T>
void unsigned_integer_t(const T value)
{
if (value <= msgpack::ftag_unsigned::max())
bytes_.put(std::uint8_t(value));
else // if multibyte
do_unsigned_integer(value);
--expected_;
}
protected:
msgpack_writer(bool integer_keys, bool needs_flush)
: writer(), bytes_(), expected_(1), integer_keys_(integer_keys), needs_flush_(needs_flush)
{}
//! \throw std::logic_error if tree was not completed
void check_complete();
//! \throw std::logic_error if incomplete msgpack tree. \return msgpack bytes
epee::byte_slice take_msgpack();
//! Flush bytes in local buffer to `do_flush(...)`
void flush()
{
do_flush({bytes_.data(), bytes_.size()});
bytes_.clear();
}
public:
msgpack_writer(const msgpack_writer&) = delete;
virtual ~msgpack_writer() noexcept;
msgpack_writer& operator=(const msgpack_writer&) = delete;
void boolean(const bool value) override final
{
if (value)
bytes_.put(std::uint8_t(msgpack::tag::True));
else
bytes_.put(std::uint8_t(msgpack::tag::False));
--expected_;
}
void integer(const int value) override final
{ integer_t(value); }
void integer(const std::intmax_t value) override final
{ integer_t(value); }
void unsigned_integer(const unsigned value) override final
{ unsigned_integer_t(value); }
void unsigned_integer(const std::uintmax_t value) override final
{ unsigned_integer_t(value); }
void real(double) override final;
//! \throw wire::exception if `source.size()` exceeds 2^32-1
void string(boost::string_ref source) override final;
//! \throw wire::exception if `source.size()` exceeds 2^32-1
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;
//! \throw wire::exception if `items` exceeds 2^32-1.
void start_array(std::size_t items) override final;
void end_array() override final { --expected_; }
//! \throw wire::exception if `items` exceeds 2^32-1
void start_object(std::size_t items) 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 { --expected_; }
};
//! Buffers entire JSON message in memory
struct msgpack_slice_writer final : msgpack_writer
{
explicit msgpack_slice_writer(bool integer_keys = false)
: msgpack_writer(integer_keys, false)
{}
//! \throw std::logic_error if incomplete JSON tree \return JSON bytes
epee::byte_slice take_bytes()
{
return msgpack_writer::take_msgpack();
}
};
//! Periodically flushes JSON data to `std::ostream`
class msgpack_stream_writer final : public msgpack_writer
{
std::ostream& dest;
virtual void do_flush(epee::span<const std::uint8_t>) override final;
public:
explicit msgpack_stream_writer(std::ostream& dest, bool integer_keys = false)
: msgpack_writer(integer_keys, 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 msgpack::to_bytes(const T& source)
{
return wire_write::to_bytes<msgpack_slice_writer>(source);
}
template<typename T, typename F = identity_>
inline void array(msgpack_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(msgpack_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(msgpack_writer& dest, const T& source)
{
wire::array(dest, source);
}
template<typename T, typename F = identity_, typename G = identity_>
inline void dynamic_object(msgpack_writer& dest, const T& source, F key_filter = F{}, G value_filter = G{})
{
// works with "lazily" computed ranges
wire_write::dynamic_object(dest, source, 0, std::move(key_filter), std::move(value_filter));
}
template<typename T, typename F, typename G>
inline void write_bytes(msgpack_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(msgpack_writer& dest, T... fields)
{
wire_write::object(dest, std::move(fields)...);
}
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2020, The Monero Project
// Copyright (c) 2020-2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@ -299,14 +299,14 @@ namespace wire_read
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)
template<typename R, typename T, unsigned I>
inline void unpack_field(std::size_t, R& source, wire::field_<T, true, I>& 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)
template<typename R, typename T, unsigned I>
inline void unpack_field(std::size_t, R& source, wire::field_<T, false, I>& dest)
{
dest.get_value().emplace();
read_bytes(source, *dest.get_value());
@ -322,7 +322,7 @@ namespace wire_read
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;
map[index].id = head.id();
expand_field_map(index + 1, map, tail...);
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2020, The Monero Project
// Copyright (c) 2020-2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@ -142,13 +142,18 @@ namespace wire_write
declared after these functions. */
template<typename W, typename T>
inline epee::byte_slice to_bytes(const T& value)
inline epee::byte_slice to_bytes(W&& dest, const T& value)
{
W dest{};
write_bytes(dest, value);
return dest.take_bytes();
}
template<typename W, typename T>
inline epee::byte_slice to_bytes(const T& value)
{
return wire_write::to_bytes(W{}, value);
}
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{})
{
@ -162,20 +167,20 @@ namespace wire_write
dest.end_array();
}
template<typename W, typename T>
inline bool field(W& dest, const wire::field_<T, true> elem)
template<typename W, typename T, unsigned I>
inline bool field(W& dest, const wire::field_<T, true, I> elem)
{
dest.key(0, elem.name);
dest.key(I, 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)
template<typename W, typename T, unsigned I>
inline bool field(W& dest, const wire::field_<T, false, I> elem)
{
if (bool(elem.get_value()))
{
dest.key(0, elem.name);
dest.key(I, elem.name);
write_bytes(dest, *elem.get_value());
}
return true;

View file

@ -33,5 +33,5 @@ target_link_libraries(monero-lws-unit-framework)
add_subdirectory(wire)
add_executable(monero-lws-unit main.cpp)
target_link_libraries(monero-lws-unit monero-lws-unit-framework monero-lws-unit-wire monero-lws-unit-wire-json)
target_link_libraries(monero-lws-unit monero-lws-unit-framework monero-lws-unit-wire monero-lws-unit-wire-json monero-lws-unit-wire-msgpack)
add_test(NAME monero-lws-unit COMMAND monero-lws-unit -v)

View file

@ -1,4 +1,4 @@
# Copyright (c) 2022, The Monero Project
# Copyright (c) 2022-2023, The Monero Project
#
# All rights reserved.
#
@ -28,6 +28,7 @@
add_subdirectory(json)
add_subdirectory(msgpack)
add_library(monero-lws-unit-wire OBJECT read.write.test.cpp read.test.cpp)
target_link_libraries(

View file

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

View file

@ -0,0 +1,183 @@
// Copyright (c) 2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "framework.test.h"
#include <boost/core/demangle.hpp>
#include <boost/range/algorithm/equal.hpp>
#include <cstdint>
#include <type_traits>
#include "wire/traits.h"
#include "wire/msgpack.h"
#include "wire/vector.h"
#include "wire/base.test.h"
namespace
{
constexpr const char basic_string[] = u8"my_string_data";
//constexpr const char basic_[] =
// u8"{\"utf8\":\"my_string_data\",\"vec\":[0,127],\"data\":\"00ff2211\",\"choice\":true}";
constexpr const std::uint8_t basic_msgpack[] = {
0x84, 0xa4, 0x75, 0x74, 0x66, 0x38, 0xae, 0x6d, 0x79, 0x5f, 0x73, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0xa3, 0x76, 0x65,
0x63, 0x92, 0x00, 0x7f, 0xa4, 0x64, 0x61, 0x74, 0x61, 0xc4, 0x04, 0x00,
0xff, 0x22, 0x11, 0xa6, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0xc3
};
constexpr const std::uint8_t advanced_msgpack[] = {
0x84, 0x00, 0xae, 0x6d, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x5f, 0x64, 0x61, 0x74, 0x61, 0x01, 0x92, 0x00, 0x7f, 0x02, 0xc4, 0x04,
0x00, 0xff, 0x22, 0x11, 0xcc, 0xfe, 0xc3
};
template<typename T>
struct basic_object
{
std::string utf8;
std::vector<T> vec;
lws_test::small_blob data;
bool choice;
};
template<typename F, typename T>
void basic_object_map(F& format, T& self)
{
wire::object(format,
WIRE_FIELD_ID(0, utf8),
WIRE_FIELD_ID(1, vec),
WIRE_FIELD_ID(2, data),
WIRE_FIELD_ID(254, choice)
);
}
template<typename T>
void read_bytes(wire::msgpack_reader& source, basic_object<T>& dest)
{ basic_object_map(source, dest); }
template<typename T>
void write_bytes(wire::msgpack_writer& dest, const basic_object<T>& source)
{ basic_object_map(dest, source); }
template<typename T>
void test_basic_reading(lest::env& lest_env)
{
SETUP("Basic (string keys) with " + boost::core::demangle(typeid(T).name()) + " integers")
{
const auto result =
wire::msgpack::from_bytes<basic_object<T>>(epee::byte_slice{{basic_msgpack}});
EXPECT(result);
EXPECT(result->utf8 == basic_string);
{
const std::vector<T> expected{0, 127};
EXPECT(result->vec == expected);
}
EXPECT(result->data == lws_test::blob_test1);
EXPECT(result->choice);
}
}
template<typename T>
void test_advanced_reading(lest::env& lest_env)
{
SETUP("Advanced (integer keys) with " + boost::core::demangle(typeid(T).name()) + " integers")
{
const auto result =
wire::msgpack::from_bytes<basic_object<T>>(epee::byte_slice{{advanced_msgpack}});
EXPECT(result);
EXPECT(result->utf8 == basic_string);
{
const std::vector<T> expected{0, 127};
EXPECT(result->vec == expected);
}
EXPECT(result->data == lws_test::blob_test1);
EXPECT(result->choice);
}
}
template<typename T>
void test_basic_writing(lest::env& lest_env)
{
SETUP("Basic (string keys) with " + boost::core::demangle(typeid(T).name()) + " integers")
{
const basic_object<T> val{basic_string, std::vector<T>{0, 127}, lws_test::blob_test1, true};
const auto result = wire::msgpack::to_bytes(val);
EXPECT(boost::range::equal(result, epee::byte_slice{{basic_msgpack}}));
}
}
template<typename T>
void test_advanced_writing(lest::env& lest_env)
{
SETUP("Advanced (integer keys) with " + boost::core::demangle(typeid(T).name()) + " integers")
{
const basic_object<T> val{basic_string, std::vector<T>{0, 127}, lws_test::blob_test1, true};
const auto result = wire_write::to_bytes(wire::msgpack_slice_writer{true}, val);
EXPECT(boost::range::equal(result, epee::byte_slice{{advanced_msgpack}}));
}
}
}
LWS_CASE("wire::msgpack_reader")
{
test_basic_reading<std::int16_t>(lest_env);
test_basic_reading<std::int32_t>(lest_env);
test_basic_reading<std::int64_t>(lest_env);
test_basic_reading<std::intmax_t>(lest_env);
test_basic_reading<std::uint16_t>(lest_env);
test_basic_reading<std::uint32_t>(lest_env);
test_basic_reading<std::uint64_t>(lest_env);
test_basic_reading<std::uintmax_t>(lest_env);
test_advanced_reading<std::int16_t>(lest_env);
test_advanced_reading<std::int32_t>(lest_env);
test_advanced_reading<std::int64_t>(lest_env);
test_advanced_reading<std::intmax_t>(lest_env);
test_advanced_reading<std::uint16_t>(lest_env);
test_advanced_reading<std::uint32_t>(lest_env);
test_advanced_reading<std::uint64_t>(lest_env);
test_advanced_reading<std::uintmax_t>(lest_env);
}
LWS_CASE("wire::msgpack_writer")
{
test_basic_writing<std::int16_t>(lest_env);
test_basic_writing<std::int32_t>(lest_env);
test_basic_writing<std::int64_t>(lest_env);
test_basic_writing<std::intmax_t>(lest_env);
test_basic_writing<std::uint16_t>(lest_env);
test_basic_writing<std::uint32_t>(lest_env);
test_basic_writing<std::uint64_t>(lest_env);
test_basic_writing<std::uintmax_t>(lest_env);
test_advanced_writing<std::int16_t>(lest_env);
test_advanced_writing<std::int32_t>(lest_env);
test_advanced_writing<std::int64_t>(lest_env);
test_advanced_writing<std::intmax_t>(lest_env);
test_advanced_writing<std::uint16_t>(lest_env);
test_advanced_writing<std::uint32_t>(lest_env);
test_advanced_writing<std::uint64_t>(lest_env);
test_advanced_writing<std::uintmax_t>(lest_env);
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2022, The Monero Project
// Copyright (c) 2022-2023, The Monero Project
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
@ -34,6 +34,7 @@
#include <vector>
#include "wire.h"
#include "wire/json.h"
#include "wire/msgpack.h"
#include "wire/vector.h"
#include "wire/base.test.h"
@ -93,7 +94,7 @@ namespace
void fill(complex& self)
{
self.objects = std::vector<inner>{inner{0, limit<std::uint32_t>::max()}, inner{100, 200}, inner{44444, 83434}};
self.ints = std::vector<std::int16_t>{limit<std::int16_t>::min(), limit<std::int16_t>::max(), 0, 31234};
self.ints = std::vector<std::int16_t>{limit<std::int16_t>::min(), limit<std::int16_t>::max(), -3, 31234};
self.uints = std::vector<std::uint64_t>{0, limit<std::uint64_t>::max(), 34234234, 33};
self.blobs = {lws_test::blob_test1, lws_test::blob_test2, lws_test::blob_test3};
self.strings = {"string1", "string2", "string3", "string4"};
@ -113,7 +114,7 @@ namespace
EXPECT(self.ints.size() == 4);
EXPECT(self.ints.at(0) == limit<std::int16_t>::min());
EXPECT(self.ints.at(1) == limit<std::int16_t>::max());
EXPECT(self.ints.at(2) == 0);
EXPECT(self.ints.at(2) == -3);
EXPECT(self.ints.at(3) == 31234);
EXPECT(self.uints.size() == 4);
@ -136,7 +137,7 @@ namespace
EXPECT(self.choice == true);
}
template<typename T>
template<typename T, typename U>
void run_complex(lest::env& lest_env)
{
SETUP("Complex test for " + boost::core::demangle(typeid(T).name()))
@ -148,7 +149,7 @@ namespace
const expect<epee::byte_slice> bytes = T::to_bytes(base);
EXPECT(bytes);
const expect<complex> derived = T::template from_bytes<complex>(std::string{bytes->begin(), bytes->end()});
const expect<complex> derived = T::template from_bytes<complex>(U{std::string{bytes->begin(), bytes->end()}});
EXPECT(derived);
verify_initial(lest_env, *derived);
}
@ -159,7 +160,7 @@ namespace
const expect<epee::byte_slice> bytes = T::to_bytes(base);
EXPECT(bytes);
const expect<complex> derived = T::template from_bytes<complex>(std::string{bytes->begin(), bytes->end()});
const expect<complex> derived = T::template from_bytes<complex>(U{std::string{bytes->begin(), bytes->end()}});
EXPECT(derived);
verify_filled(lest_env, *derived);
}
@ -180,7 +181,7 @@ namespace
WIRE_DEFINE_OBJECT(big, big_map)
WIRE_DEFINE_OBJECT(small, small_map)
template<typename T>
template<typename T, typename U>
expect<small> round_trip(lest::env& lest_env, std::int64_t value)
{
expect<small> out = small{0};
@ -188,43 +189,77 @@ namespace
{
const expect<epee::byte_slice> bytes = T::template to_bytes(big{value});
EXPECT(bytes);
out = T::template from_bytes<small>(std::string{bytes->begin(), bytes->end()});
out = T::template from_bytes<small>(U{std::string{bytes->begin(), bytes->end()}});
}
return out;
}
template<typename T>
template<typename T, typename U>
void not_overflow(lest::env& lest_env, std::int64_t value)
{
const expect<small> result = round_trip<T>(lest_env, value);
const expect<small> result = round_trip<T, U>(lest_env, value);
EXPECT(result);
EXPECT(result->value == value);
}
template<typename T>
template<typename T, typename U>
void overflow(lest::env& lest_env, std::int64_t value, const std::error_code error)
{
const expect<small> result = round_trip<T>(lest_env, value);
const expect<small> result = round_trip<T, U>(lest_env, value);
EXPECT(result == error);
}
template<typename T>
template<typename T, typename U>
void run_overflow(lest::env& lest_env)
{
SETUP("Overflow test for " + boost::core::demangle(typeid(T).name()))
{
not_overflow<T>(lest_env, limit<std::int32_t>::min());
not_overflow<T>(lest_env, 0);
not_overflow<T>(lest_env, limit<std::int32_t>::max());
not_overflow<T, U>(lest_env, limit<std::int32_t>::min());
not_overflow<T, U>(lest_env, 0);
not_overflow<T, U>(lest_env, limit<std::int32_t>::max());
overflow<T>(lest_env, std::int64_t(limit<std::int32_t>::min()) - 1, wire::error::schema::larger_integer);
overflow<T>(lest_env, std::int64_t(limit<std::int32_t>::max()) + 1, wire::error::schema::smaller_integer);
overflow<T, U>(lest_env, std::int64_t(limit<std::int32_t>::min()) - 1, wire::error::schema::larger_integer);
overflow<T, U>(lest_env, std::int64_t(limit<std::int32_t>::max()) + 1, wire::error::schema::smaller_integer);
}
}
struct simple { bool choice; };
static void read_bytes(wire::reader& source, simple& self)
{
wire::object(source, WIRE_FIELD(choice));
}
template<typename T, typename U>
void run_skip(lest::env& lest_env)
{
complex base{};
verify_initial(lest_env, base);
fill(base);
const expect<epee::byte_slice> bytes = T::to_bytes(base);
EXPECT(bytes);
const expect<simple> derived = T::template from_bytes<simple>(U{std::string{bytes->begin(), bytes->end()}});
EXPECT(derived);
EXPECT(derived->choice);
}
}
LWS_CASE("wire::reader and wire::writer")
LWS_CASE("wire::reader and wire::writer complex")
{
run_complex<wire::json>(lest_env);
run_overflow<wire::json>(lest_env);
run_complex<wire::json, std::string>(lest_env);
run_complex<wire::msgpack, epee::byte_slice>(lest_env);
}
LWS_CASE("wire::reader and wire::writer overflow")
{
run_overflow<wire::json, std::string>(lest_env);
run_overflow<wire::msgpack, epee::byte_slice>(lest_env);
}
LWS_CASE("wire::reader and wire::writer skip")
{
run_skip<wire::json, std::string>(lest_env);
run_skip<wire::msgpack, epee::byte_slice>(lest_env);
}