Skip to content

Commit

Permalink
Add polymorphic allocating byte_reader methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
evoskuil committed Jul 7, 2024
1 parent c61c891 commit 1cfda5f
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 54 deletions.
148 changes: 121 additions & 27 deletions include/bitcoin/system/impl/stream/streamers/byte_reader.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
#ifndef LIBBITCOIN_SYSTEM_STREAM_STREAMERS_BYTE_READER_IPP
#define LIBBITCOIN_SYSTEM_STREAM_STREAMERS_BYTE_READER_IPP

#include <algorithm>
#include <ios>
#include <istream>
#include <limits>
#include <memory>
#include <memory_resource>
#include <string>
#include <bitcoin/system/data/data.hpp>
#include <bitcoin/system/define.hpp>
Expand All @@ -33,14 +36,25 @@
namespace libbitcoin {
namespace system {

// private/static
template <typename IStream>
inline byte_reader<IStream>::memory_resource*
byte_reader<IStream>::default_allocator() NOEXCEPT
{
return std::pmr::get_default_resource();
}

// All public methods must rely on protected for stream state except validity.

// constructors
// ----------------------------------------------------------------------------

template <typename IStream>
byte_reader<IStream>::byte_reader(IStream& source) NOEXCEPT
: stream_(source), remaining_(system::maximum<size_t>)
byte_reader<IStream>::byte_reader(IStream& source,
memory_resource* allocator) NOEXCEPT
: stream_(source),
remaining_(system::maximum<size_t>),
allocator_(allocator)
{
////BC_ASSERT_MSG(stream_.exceptions() == IStream::goodbit,
//// "Input stream must not be configured to throw exceptions.");
Expand Down Expand Up @@ -201,7 +215,21 @@ code byte_reader<IStream>::read_error_code() NOEXCEPT
return code(static_cast<error::error_t>(value));
}

// bytes
template <typename IStream>
uint8_t byte_reader<IStream>::peek_byte() NOEXCEPT
{
return do_peek_byte();
}

template <typename IStream>
uint8_t byte_reader<IStream>::read_byte() NOEXCEPT
{
uint8_t value = pad();
do_read_bytes(&value, one);
return value;
}

// byte arrays
// ----------------------------------------------------------------------------

template <typename IStream>
Expand All @@ -223,12 +251,25 @@ data_array<Size> byte_reader<IStream>::read_reverse() NOEXCEPT
}

template <typename IStream>
std::ostream& byte_reader<IStream>::read(std::ostream& out) NOEXCEPT
template <size_t Size>
data_array_cptr<Size> byte_reader<IStream>::read_forward_cptr() NOEXCEPT
{
// This creates an intermediate buffer the size of the stream.
// This is presumed to be more optimal than looping individual bytes.
byte_writer<std::ostream>(out).write_bytes(read_bytes());
return out;
// Truncated bytes are populated with 0x00.
// Reader supports directly populating an array, this avoids a copy.
const auto cptr = to_allocated<data_array<Size>>(allocator_);
const auto ptr = const_cast<data_array<Size>*>(cptr.get());
do_read_bytes(ptr->data(), Size);
return cptr;
}

template <typename IStream>
template <size_t Size>
data_array_cptr<Size> byte_reader<IStream>::read_reverse_cptr() NOEXCEPT
{
const auto cptr = read_forward_cptr<Size>();
const auto ptr = const_cast<data_array<Size>*>(cptr.get());
std::reverse(ptr->begin(), ptr->end());
return cptr;
}

template <typename IStream>
Expand Down Expand Up @@ -256,35 +297,60 @@ long_hash byte_reader<IStream>::read_long_hash() NOEXCEPT
}

template <typename IStream>
uint8_t byte_reader<IStream>::peek_byte() NOEXCEPT
mini_hash_cptr byte_reader<IStream>::read_mini_hash_cptr() NOEXCEPT
{
return do_peek_byte();
return read_forward_cptr<mini_hash_size>();
}

template <typename IStream>
uint8_t byte_reader<IStream>::read_byte() NOEXCEPT
short_hash_cptr byte_reader<IStream>::read_short_hash_cptr() NOEXCEPT
{
uint8_t value = pad();
do_read_bytes(&value, one);
return value;
return read_forward_cptr<short_hash_size>();
}

template <typename IStream>
hash_cptr byte_reader<IStream>::read_hash_cptr() NOEXCEPT
{
return read_forward_cptr<hash_size>();
}

template <typename IStream>
long_hash_cptr byte_reader<IStream>::read_long_hash_cptr() NOEXCEPT
{
return read_forward_cptr<long_hash_size>();
}

// byte vectors
// ----------------------------------------------------------------------------

template <typename IStream>
data_chunk byte_reader<IStream>::read_bytes() NOEXCEPT
{
// Isolating get_exhausted to first call is an optimization (must clear).
if (get_exhausted())
return {};
// Count bytes to the end, avoids push_back reallocations.
size_t size{};
while (!get_exhausted())
{
++size;
skip_byte();
};

// This will always produce at least one (zero) terminating byte.
data_chunk out{};
while (valid())
out.push_back(read_byte());
rewind_bytes(size);
return read_bytes(size);
}

clear();
out.resize(sub1(out.size()));
out.shrink_to_fit();
return out;
template <typename IStream>
chunk_cptr byte_reader<IStream>::read_bytes_cptr() NOEXCEPT
{
// Count bytes to the end, avoids push_back reallocations.
size_t size{};
while (!get_exhausted())
{
++size;
skip_byte();
};

rewind_bytes(size);
return read_bytes_cptr(size);
}

template <typename IStream>
Expand All @@ -293,7 +359,7 @@ data_chunk byte_reader<IStream>::read_bytes(size_t size) NOEXCEPT
if (is_zero(size))
return {};

// This allows caller read an invalid stream without allocation.
// This allows caller to read an invalid stream without allocation.
if (!valid())
return{};

Expand All @@ -302,6 +368,22 @@ data_chunk byte_reader<IStream>::read_bytes(size_t size) NOEXCEPT
return out;
}

template <typename IStream>
chunk_cptr byte_reader<IStream>::read_bytes_cptr(size_t size) NOEXCEPT
{
if (is_zero(size))
return {};

// This allows caller to read an invalid stream without allocation.
if (!valid())
return{};

const auto cptr = to_allocated<data_chunk>(allocator_, size);
const auto ptr = const_cast<data_chunk*>(cptr.get());
do_read_bytes(ptr->data(), size);
return cptr;
}

template <typename IStream>
void byte_reader<IStream>::read_bytes(uint8_t* buffer, size_t size) NOEXCEPT
{
Expand Down Expand Up @@ -333,12 +415,24 @@ std::string byte_reader<IStream>::read_string_buffer(size_t size) NOEXCEPT
// Removes zero and all after, required for bitcoin string deserialization.
const auto position = out.find('\0');
out.resize(position == std::string::npos ? out.size() : position);
out.shrink_to_fit();
////out.shrink_to_fit();

clear();
return out;
}

// streams
// ----------------------------------------------------------------------------

template <typename IStream>
std::ostream& byte_reader<IStream>::read(std::ostream& out) NOEXCEPT
{
// This creates an intermediate buffer the size of the stream.
// This is presumed to be more optimal than looping individual bytes.
byte_writer<std::ostream>(out).write_bytes(read_bytes());
return out;
}

// context
// ----------------------------------------------------------------------------

Expand Down
60 changes: 50 additions & 10 deletions include/bitcoin/system/stream/streamers/byte_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <iostream>
#include <limits>
#include <memory_resource>
#include <string>
#include <bitcoin/system/data/data.hpp>
#include <bitcoin/system/define.hpp>
Expand All @@ -30,17 +31,23 @@

namespace libbitcoin {
namespace system {

/// A byte reader that accepts an istream.
template <typename IStream = std::istream>
class byte_reader
: public virtual bytereader
{
public:
using memory_resource = std::pmr::memory_resource;

DEFAULT_COPY_MOVE_DESTRUCT(byte_reader);

/// Constructors.
byte_reader(IStream& source) NOEXCEPT;
byte_reader(IStream& source,
memory_resource* allocator=default_allocator()) NOEXCEPT;

/// Integrals.
/// -----------------------------------------------------------------------

/// Read integer, size determined from parameter type.
template <typename Integer, size_t Size = sizeof(Integer),
Expand Down Expand Up @@ -80,32 +87,54 @@ class byte_reader
/// Convert read_4_bytes_little_endian to an error code.
code read_error_code() NOEXCEPT override;

/// Read/peek one byte (invalidates an empty stream).
uint8_t peek_byte() NOEXCEPT override;
uint8_t read_byte() NOEXCEPT override;

/// Bytes Arrays.
/// -----------------------------------------------------------------------

/// Read size bytes into array.
template <size_t Size>
data_array<Size> read_forward() NOEXCEPT;
template <size_t Size>
data_array<Size> read_reverse() NOEXCEPT;

/// Read into stream until buffer is exhausted.
std::ostream& read(std::ostream& out) NOEXCEPT override;
/// Read size bytes into pointer to const array.
template <size_t Size>
data_array_cptr<Size> read_forward_cptr() NOEXCEPT;
template <size_t Size>
data_array_cptr<Size> read_reverse_cptr() NOEXCEPT;

/// Read hash (explicit specializations of read_forward).
/// Read hash to stack allocated forwarded object.
mini_hash read_mini_hash() NOEXCEPT override;
short_hash read_short_hash() NOEXCEPT override;
hash_digest read_hash() NOEXCEPT override;
long_hash read_long_hash() NOEXCEPT override;

/// Read/peek one byte (invalidates an empty stream).
uint8_t peek_byte() NOEXCEPT override;
uint8_t read_byte() NOEXCEPT override;
/// Read hash to heap allocated object owned by shared pointer.
mini_hash_cptr read_mini_hash_cptr() NOEXCEPT override;
short_hash_cptr read_short_hash_cptr() NOEXCEPT override;
hash_cptr read_hash_cptr() NOEXCEPT override;
long_hash_cptr read_long_hash_cptr() NOEXCEPT override;

/// Read all remaining bytes.
/// Bytes Vectors.
/// -----------------------------------------------------------------------

/// Read all remaining bytes to chunk.
data_chunk read_bytes() NOEXCEPT override;
chunk_cptr read_bytes_cptr() NOEXCEPT override;

/// Read size bytes, return size is guaranteed.
/// Read size bytes to chunk, return size is guaranteed.
data_chunk read_bytes(size_t size) NOEXCEPT override;
chunk_cptr read_bytes_cptr(size_t size) NOEXCEPT override;

/// Read size bytes to buffer, return size is guaranteed.
void read_bytes(uint8_t* buffer, size_t size) NOEXCEPT override;

/// Strings.
/// -----------------------------------------------------------------------

/// Read Bitcoin length-prefixed string.
/// Returns empty and invalidates stream if would exceed read limit.
std::string read_string(size_t limit=max_size_t) NOEXCEPT override;
Expand All @@ -114,6 +143,15 @@ class byte_reader
/// This is only used for reading Bitcoin heading command text.
std::string read_string_buffer(size_t size) NOEXCEPT override;

/// Streams.
/// -----------------------------------------------------------------------

/// Read into stream until buffer is exhausted.
std::ostream& read(std::ostream& out) NOEXCEPT override;

/// Control.
/// -----------------------------------------------------------------------

/// Advance the iterator.
void skip_byte() NOEXCEPT override;
void skip_bytes(size_t size) NOEXCEPT override;
Expand Down Expand Up @@ -162,6 +200,7 @@ class byte_reader
virtual bool get_exhausted() const NOEXCEPT;

private:
static inline memory_resource* default_allocator() NOEXCEPT;
bool valid() const NOEXCEPT;
void invalid() NOEXCEPT;
void validate() NOEXCEPT;
Expand All @@ -173,6 +212,7 @@ class byte_reader

IStream& stream_;
size_t remaining_;
memory_resource* allocator_;
};

} // namespace system
Expand Down
Loading

0 comments on commit 1cfda5f

Please sign in to comment.