// 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 #include #include #include #include #include #include #include #include "wire/error.h" #include "wire/msgpack/error.h" namespace { template using limits = std::numeric_limits; 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 void write_endian(epee::byte_stream& bytes, const T value, const wire::msgpack::type type) { static_assert(std::is_integral::value, "input not integral"); static_assert(std::is_integral::value, "output not integral"); using in_limits = std::numeric_limits; using out_limits = std::numeric_limits; 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; buffer_type buffer(value); write_tag(bytes, type.Tag()); bytes.write(buffer.data(), sizeof(buffer)); } template 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 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(bytes, items); } } namespace wire { void msgpack_writer::do_flush(epee::span) {} 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_stream msgpack_writer::take_msgpack() { check_complete(); epee::byte_stream 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(std::addressof(source)), sizeof(source)); --expected_; } void msgpack_writer::string(const boost::string_ref source) { write_count(bytes_, source.size()); bytes_.write(source.data(), source.size()); --expected_; } void msgpack_writer::binary(epee::span source) { write_count(bytes_, source.size()); bytes_.write(source); --expected_; } void msgpack_writer::enumeration(const std::size_t index, const epee::span 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(bytes_, items); expected_ += items; } void msgpack_writer::start_object(const std::size_t items) { write_count(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 bytes) { dest.write(reinterpret_cast(bytes.data()), bytes.size()); } }