forked from such-gitea/wownero-lws
510 lines
17 KiB
C++
510 lines
17 KiB
C++
// 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::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;
|
|
}
|
|
}
|