// Copyright (c) 2020-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 #include #include #include "byte_slice.h" #include "span.h" namespace epee { /*! \brief A partial drop-in replacement for `std::ostream`. Only a few base `std::ostream` functions are implemented - enough for rapidjson output currently. Advantages over `std::stringstream` or `rapidjson::StringBuffer`: - The internal buffer can be taken without a copy. - The internal buffer can be given to `byte_slice` with zero allocations for reference count. - The internal buffer can be given to `zmq_msg_data_init` without a copy or extra allocation. an additional advantage over `std::stringstream`: - Construction is significantly faster - the global `std::locale` does not have to be acquired (global thread synchronization), and an extra allocation for `std::stringbuf` is not needed (which is an addition to the buffer inside of that object). */ class byte_stream { byte_buffer buffer_; //! Beginning of buffer std::uint8_t* next_write_; //! Current write position const std::uint8_t* end_; //! End of buffer //! \post `requested <= available()` void overflow(const std::size_t requested); //! Ensures that at least `requested` bytes are available. void check(const std::size_t requested) { const std::size_t remaining = available(); if (remaining < requested) overflow(requested); } public: using char_type = std::uint8_t; using Ch = char_type; //! Increase internal buffer by at least `byte_stream_increase` bytes. byte_stream() noexcept : buffer_(nullptr), next_write_(nullptr), end_(nullptr) {} byte_stream(byte_stream&& rhs) noexcept; ~byte_stream() noexcept = default; byte_stream& operator=(byte_stream&& rhs) noexcept; const std::uint8_t* data() const noexcept { return buffer_.get(); } std::uint8_t* tellp() const noexcept { return next_write_; } std::size_t available() const noexcept { return end_ - next_write_; } std::size_t size() const noexcept { return next_write_ - buffer_.get(); } std::size_t capacity() const noexcept { return end_ - buffer_.get(); } //! Compatibility with rapidjson. void Flush() const noexcept {} /*! Reserve at least `more` bytes. \post `size() + more <= available()`. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void reserve(const std::size_t more) { check(more); } //! Reset write position, but do not release internal memory. \post `size() == 0`. void clear() noexcept { next_write_ = buffer_.get(); } /*! Copy `length` bytes starting at `ptr` to end of stream. \throw std::range_error If exceeding max size_t value. \throw std::bad_alloc If allocation fails. */ void write(const std::uint8_t* ptr, const std::size_t length) { check(length); std::memcpy(tellp(), ptr, length); next_write_ += length; } /*! Copy `length` bytes starting at `ptr` to end of stream. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void write(const char* ptr, const std::size_t length) { write(reinterpret_cast(ptr), length); } /*! Copy `source` to end of stream. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void write(const epee::span source) { write(source.data(), source.size()); } /*! Copy `source` to end of stream. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void write(const epee::span source) { write(source.data(), source.size()); } /*! Copy `ch` to end of stream. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void put(const std::uint8_t ch) { check(1); put_unsafe(ch); } /*! Copy `ch` to end of stream. Provides rapidjson compatability. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void Put(const std::uint8_t ch) { put(ch); } /*! Writes `ch` to end of stream without runtime capacity checks. Must use `reserve` before calling this function. Primarily for use with rapidjson, which writes characters at a time but reserves memory in blocks. Most applications want to use `put` or `write`. */ void put_unsafe(const std::uint8_t ch) noexcept { assert(1 <= available()); *(tellp()) = ch; ++next_write_; } /*! Write `ch` to end of stream `count` times. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void put_n(const std::uint8_t ch, const std::size_t count) { check(count); std::memset(tellp(), ch, count); next_write_ += count; } /*! Copy `ch` to end of stream. \throw std::range_error if exceeding max `size_t` value. \throw std::bad_alloc if allocation fails. */ void push_back(const std::uint8_t ch) { put(ch); } //! \return The internal buffer. \post `size() == capacity() == 0`. byte_buffer take_buffer() noexcept; }; //! Compatability/optimization for rapidjson. inline void PutReserve(byte_stream& dest, const std::size_t length) { dest.reserve(length); } //! Compatability/optimization for rapidjson. inline void PutUnsafe(byte_stream& dest, const std::uint8_t ch) { dest.put_unsafe(ch); } //! Compability/optimization for rapidjson. inline void PutN(byte_stream& dest, const std::uint8_t ch, const std::size_t count) { dest.put_n(ch, count); } } // epee