// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#pragma once
// #include <hexi/binary_stream.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/shared.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#include <algorithm>
#include <array>
#include <bit>
#include <concepts>
#include <type_traits>
#include <cstddef>
#include <cstdint>
namespace hexi {
struct is_contiguous {};
struct is_non_contiguous {};
struct supported {};
struct unsupported {};
struct except_tag{};
struct allow_throw : except_tag{};
struct no_throw : except_tag{};
enum class buffer_seek {
sk_absolute, sk_backward, sk_forward
};
enum class stream_seek {
// Seeks within the entire underlying buffer
sk_buffer_absolute,
sk_backward,
sk_forward,
// Seeks only within the range written by the current stream
sk_stream_absolute
};
enum class stream_state {
ok,
read_limit_err,
buff_limit_err,
invalid_stream,
user_defined_err
};
template<decltype(auto) size>
static constexpr auto generate_filled(const std::uint8_t value) {
std::array<std::uint8_t, size> target{};
std::ranges::fill(target, value);
return target;
}
namespace detail {
// Returns true if there's any overlap between source and destination ranges
static inline bool region_overlap(const void* src, std::size_t src_len, const void* dst, std::size_t dst_len) {
const auto src_beg = std::bit_cast<std::uintptr_t>(src);
const auto src_end = src_beg + src_len;
const auto dst_beg = std::bit_cast<std::uintptr_t>(dst);
const auto dst_end = dst_beg + dst_len;
// cannot assume src is before dst or vice versa
return (src_beg >= dst_beg && src_beg < dst_end)
|| (src_end > dst_beg && src_end <= dst_end)
|| (dst_beg >= src_beg && dst_beg < src_end)
|| (dst_end > src_beg && dst_end <= src_end);
}
} // detail
} // hexi
// #include <hexi/concepts.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/shared.h>
#include <bit>
#include <concepts>
#include <type_traits>
namespace hexi {
template<typename buf_type>
concept writeable =
requires(buf_type t, void* v, typename buf_type::size_type s) {
{ t.write(v, s) } -> std::same_as<void>;
};
template<typename buf_type>
concept seekable = requires(buf_type t) {
std::is_same_v<typename buf_type::seeking, supported>;
};
template<typename buf_type>
concept contiguous = requires(buf_type t) {
std::is_same_v<typename buf_type::contiguous, is_contiguous>;
};
template<typename T>
concept arithmetic = std::integral<T> || std::floating_point<T>;
template<typename T>
concept byte_type = sizeof(T) == 1;
template<typename T>
concept byte_oriented = byte_type<typename T::value_type>;
template<typename T>
concept pod = std::is_standard_layout_v<T> && std::is_trivial_v<T>;
template<typename T>
concept has_resize_overwrite =
requires(T t) {
{ t.resize_and_overwrite(typename T::size_type(), [](char*, T::size_type) {}) } -> std::same_as<void>;
};
template<typename T>
concept has_resize =
requires(T t) {
{ t.resize(typename T::size_type() ) } -> std::same_as<void>;
};
template<typename T, typename U>
concept has_shl_override =
requires(T t, U& u) {
{ t.operator<<(u) } -> std::same_as<U&>;
};
template<typename T, typename U>
concept has_shr_override =
requires(T t, U& u) {
{ t.operator>>(u) } -> std::same_as<U&>;
};
} // hexi
// #include <hexi/exception.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#include <format>
#include <stdexcept>
#include <utility>
#include <cstddef>
namespace hexi {
class exception : public std::runtime_error {
public:
exception(std::string msg)
: std::runtime_error(std::move(msg)) {}
};
class buffer_underrun final : public exception {
public:
const std::size_t buff_size, read_size, total_read;
buffer_underrun(std::size_t read_size, std::size_t total_read, std::size_t buff_size)
: exception(std::format(
"Buffer underrun: {} byte read requested, buffer contains {} bytes and total bytes read was {}",
read_size, buff_size, total_read)),
buff_size(buff_size), read_size(read_size), total_read(total_read) {}
};
class buffer_overflow final : public exception {
public:
const std::size_t free, write_size, total_write;
buffer_overflow(std::size_t write_size, std::size_t total_write, std::size_t free)
: exception(std::format(
"Buffer overflow: {} byte write requested, free space is {} bytes and total bytes written was {}",
write_size, free, total_write)),
free(free), write_size(write_size), total_write(total_write) {}
};
class stream_read_limit final : public exception {
public:
const std::size_t read_limit, read_size, total_read;
stream_read_limit(std::size_t read_size, std::size_t total_read, std::size_t read_limit)
: exception(std::format(
"Read boundary exceeded: {} byte read requested, read limit was {} bytes and total bytes read was {}",
read_size, read_limit, total_read)),
read_limit(read_limit), read_size(read_size), total_read(total_read) {}
};
} // hexi
// #include <hexi/endian.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/concepts.h>
#include <bit>
#include <type_traits>
#include <utility>
#include <cstdint>
namespace hexi::endian {
enum class conversion {
big_to_native,
native_to_big,
little_to_native,
native_to_little
};
constexpr auto conditional_reverse(arithmetic auto value, std::endian from, std::endian to) {
using type = decltype(value);
if(from != to) {
if constexpr(std::is_same_v<type, float>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint32_t>(value)));
} else if constexpr(std::is_same_v<type, double>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint64_t>(value)));
} else {
value = std::byteswap(value);
}
}
return value;
}
template<std::endian from, std::endian to>
constexpr auto conditional_reverse(arithmetic auto value) {
using type = decltype(value);
if constexpr(from != to) {
if constexpr(std::is_same_v<type, float>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint32_t>(value)));
} else if constexpr(std::is_same_v<type, double>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint64_t>(value)));
} else {
value = std::byteswap(value);
}
}
return value;
}
constexpr auto little_to_native(arithmetic auto value) {
return conditional_reverse<std::endian::little, std::endian::native>(value);
}
constexpr auto big_to_native(arithmetic auto value) {
return conditional_reverse<std::endian::big, std::endian::native>(value);
}
constexpr auto native_to_little(arithmetic auto value) {
return conditional_reverse<std::endian::native, std::endian::little>(value);
}
constexpr auto native_to_big(arithmetic auto value) {
return conditional_reverse<std::endian::native, std::endian::big>(value);
}
template<std::endian from, std::endian to>
constexpr void conditional_reverse_inplace(arithmetic auto& value) {
using type = std::remove_reference_t<decltype(value)>;
if constexpr(from != to) {
if constexpr(std::is_same_v<type, float>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint32_t>(value)));
} else if constexpr(std::is_same_v<type, double>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint64_t>(value)));
} else {
value = std::byteswap(value);
}
}
}
constexpr void conditional_reverse_inplace(arithmetic auto& value, std::endian from, std::endian to) {
using type = std::remove_reference_t<decltype(value)>;
if(from != to) {
if constexpr(std::is_same_v<type, float>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint32_t>(value)));
} else if constexpr(std::is_same_v<type, double>) {
value = std::bit_cast<type>(std::byteswap(std::bit_cast<std::uint64_t>(value)));
} else {
value = std::byteswap(value);
}
}
}
constexpr void little_to_native_inplace(arithmetic auto& value) {
conditional_reverse_inplace<std::endian::little, std::endian::native>(value);
}
constexpr void big_to_native_inplace(arithmetic auto& value) {
conditional_reverse_inplace<std::endian::big, std::endian::native>(value);
}
constexpr void native_to_little_inplace(arithmetic auto& value) {
conditional_reverse_inplace<std::endian::native, std::endian::little>(value);
}
constexpr void native_to_big_inplace(arithmetic auto& value) {
conditional_reverse_inplace<std::endian::native, std::endian::big>(value);
}
template<conversion _conversion>
constexpr auto convert(arithmetic auto value) -> decltype(value) {
switch(_conversion) {
case conversion::big_to_native:
return big_to_native(value);
case conversion::native_to_big:
return native_to_big(value);
case conversion::little_to_native:
return little_to_native(value);
case conversion::native_to_little:
return native_to_little(value);
default:
std::unreachable();
};
}
} // endian, hexi
#include <algorithm>
#include <concepts>
#include <ranges>
#include <span>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
namespace hexi {
using namespace detail;
#define STREAM_READ_BOUNDS_CHECK(read_size, ret_var) \
check_read_bounds(read_size); \
\
if constexpr(std::is_same_v<exceptions, no_throw>) { \
if(state_ != stream_state::ok) [[unlikely]] { \
return ret_var; \
} \
}
template<byte_oriented buf_type, std::derived_from<except_tag> exceptions = allow_throw>
class binary_stream final {
public:
using size_type = typename buf_type::size_type;
using offset_type = typename buf_type::offset_type;
using seeking = typename buf_type::seeking;
using value_type = typename buf_type::value_type;
using contiguous_type = typename buf_type::contiguous;
private:
using cond_size_type = std::conditional_t<writeable<buf_type>, size_type, std::monostate>;
buf_type& buffer_;
[[no_unique_address]] cond_size_type total_write_{};
size_type total_read_ = 0;
stream_state state_ = stream_state::ok;
const size_type read_limit_;
inline void check_read_bounds(const size_type read_size) {
if(read_size > buffer_.size()) [[unlikely]] {
state_ = stream_state::buff_limit_err;
if constexpr(std::is_same_v<exceptions, allow_throw>) {
throw buffer_underrun(read_size, total_read_, buffer_.size());
}
return;
}
const auto max_read_remaining = read_limit_ - total_read_;
if(read_limit_ && read_size > max_read_remaining) [[unlikely]] {
state_ = stream_state::read_limit_err;
if constexpr(std::is_same_v<exceptions, allow_throw>) {
throw stream_read_limit(read_size, total_read_, read_limit_);
}
return;
}
total_read_ += read_size;
}
public:
explicit binary_stream(buf_type& source, size_type read_limit = 0)
: buffer_(source),
read_limit_(read_limit) {};
binary_stream(binary_stream&& rhs) noexcept
: buffer_(rhs.buffer_),
total_write_(rhs.total_write_),
total_read_(rhs.total_read_),
state_(rhs.state_),
read_limit_(rhs.read_limit_) {
rhs.total_read_ = static_cast<size_type>(-1);
rhs.state_ = stream_state::invalid_stream;
}
binary_stream& operator=(binary_stream&&) = delete;
binary_stream& operator=(binary_stream&) = delete;
binary_stream(binary_stream&) = delete;
/*** Write ***/
binary_stream& operator <<(has_shl_override<binary_stream> auto&& data)
requires(writeable<buf_type>) {
return data.operator<<(*this);
}
template<pod T>
requires (!has_shl_override<T, binary_stream>)
binary_stream& operator <<(const T& data) requires(writeable<buf_type>) {
buffer_.write(&data, sizeof(T));
total_write_ += sizeof(T);
return *this;
}
binary_stream& operator <<(const std::string& data) requires(writeable<buf_type>) {
buffer_.write(data.data(), data.size() + 1); // +1 also writes terminator
total_write_ += (data.size() + 1);
return *this;
}
binary_stream& operator <<(const char* data) requires(writeable<buf_type>) {
assert(data);
const auto len = std::strlen(data);
buffer_.write(data, len + 1); // include terminator
total_write_ += len + 1;
return *this;
}
binary_stream& operator <<(std::string_view& data) requires(writeable<buf_type>) {
buffer_.write(data.data(), data.size());
const char term = '\0';
buffer_.write(&term, sizeof(term));
total_write_ += (data.size() + 1);
return *this;
}
/**
* @brief Writes a contiguous range to the stream.
*
* @param data The contiguous range to be written to the stream.
*/
template<std::ranges::contiguous_range range>
void put(const range& data) requires(writeable<buf_type>) {
const auto write_size = data.size() * sizeof(typename range::value_type);
buffer_.write(data.data(), write_size);
total_write_ += write_size;
}
/**
* @brief Writes a the provided value to the stream.
*
* @param data The value to be written to the stream.
*/
void put(const arithmetic auto& data) requires(writeable<buf_type>) {
buffer_.write(&data, sizeof(data));
total_write_ += sizeof(data);
}
/**
* @brief Writes data to the stream.
*
* @param data The element to be written to the stream.
*/
template<endian::conversion conversion>
void put(const arithmetic auto& data) requires(writeable<buf_type>) {
const auto swapped = endian::convert<conversion>(data);
buffer_.write(&swapped, sizeof(data));
total_write_ += sizeof(data);
}
/**
* @brief Writes count elements from the provided buffer to the stream.
*
* @param data Pointer to the buffer from which data will be copied to the stream.
* @param count The number of elements to write.
*/
template<pod T>
void put(const T* data, size_type count) requires(writeable<buf_type>) {
const auto write_size = count * sizeof(T);
buffer_.write(data, write_size);
total_write_ += write_size;
}
/**
* @brief Writes the data from the iterator range to the stream.
*
* @param begin Iterator to the beginning of the data.
* @param end Iterator to the end of the data.
*/
template<typename It>
void put(It begin, const It end) requires(writeable<buf_type>) {
for(auto it = begin; it != end; ++it) {
*this << *it;
}
}
/**
* @brief Allows for writing a provided byte value a specified number of times to
* the stream.
*
* @param The byte value that will fill the specified number of bytes.
*/
template<size_type size>
void fill(const std::uint8_t value) requires(writeable<buf_type>) {
const auto filled = generate_filled<size>(value);
buffer_.write(filled.data(), filled.size());
total_write_ += size;
}
/*** Read ***/
// terminates when it hits a null byte, empty string if none found
binary_stream& operator>>(std::string& dest) {
auto pos = buffer_.find_first_of(value_type(0));
if(pos == buf_type::npos) {
dest.clear();
return *this;
}
dest.resize_and_overwrite(pos, [&](char* strbuf, size_type size) {
buffer_.read(strbuf, size);
total_read_ += size;
return size;
});
total_read_ += 1;
buffer_.skip(1); // skip null term
return *this;
}
// terminates when it hits a null byte, empty string_view if none found
// goes without saying that the buffer must outlive the string_view
binary_stream& operator>>(std::string_view& dest) requires(contiguous<buf_type>) {
dest = view();
return *this;
}
binary_stream& operator>>(has_shr_override<binary_stream> auto&& data) {
return data.operator>>(*this);
}
template<pod T>
requires (!has_shr_override<T, binary_stream>)
binary_stream& operator>>(T& data) {
STREAM_READ_BOUNDS_CHECK(sizeof(data), *this);
buffer_.read(&data, sizeof(data));
return *this;
}
/**
* @brief Read an arithmetic type from the stream.
*
* @return The destination for the read value.
*/
void get(arithmetic auto& dest) {
STREAM_READ_BOUNDS_CHECK(sizeof(dest), void());
buffer_.read(&dest, sizeof(dest));
}
/**
* @brief Read an arithmetic type from the stream.
*
* @return The arithmetic value.
*/
template<arithmetic T>
T get() {
STREAM_READ_BOUNDS_CHECK(sizeof(T), void());
T t{};
buffer_.read(&t, sizeof(T));
return t;
}
/**
* @brief Read an arithmetic type from the stream, allowing for endian
* conversion.
*
* @param The destination for the read value.
*/
template<endian::conversion conversion>
void get(arithmetic auto& dest) {
STREAM_READ_BOUNDS_CHECK(sizeof(dest), void());
buffer_.read(&dest, sizeof(dest));
dest = endian::convert<conversion>(dest);
}
/**
* @brief Read an arithmetic type from the stream, allowing for endian
* conversion.
*
* @return The arithmetic value.
*/
template<arithmetic T, endian::conversion conversion>
T get() {
STREAM_READ_BOUNDS_CHECK(sizeof(T), void());
T t{};
buffer_.read(&t, sizeof(T));
return endian::convert<conversion>(t);
}
/**
* @brief Reads a string from the stream.
*
* @param dest The destination string.
*/
void get(std::string& dest) {
*this >> dest;
}
/**
* @brief Reads a fixed-length string from the stream.
*
* @param dest The destination string.
* @param count The number of bytes to be read.
*/
void get(std::string& dest, size_type size) {
STREAM_READ_BOUNDS_CHECK(size, void());
dest.resize_and_overwrite(size, [&](char* strbuf, size_type len) {
buffer_.read(strbuf, len);
return len;
});
}
/**
* @brief Read data from the stream into the provided destination argument.
*
* @param dest The destination buffer.
* @param count The number of bytes to be read into the destination.
*/
template<typename T>
void get(T* dest, size_type count) {
assert(dest);
const auto read_size = count * sizeof(T);
STREAM_READ_BOUNDS_CHECK(read_size, void());
buffer_.read(dest, read_size);
}
/**
* @brief Read data from the stream to the destination represented by the iterators.
*
* @param begin The beginning iterator.
* @param end The end iterator.
*/
template<typename It>
void get(It begin, const It end) {
for(; begin != end; ++begin) {
*this >> *begin;
}
}
/**
* @brief Read data from the stream into the provided destination argument.
*
* @param dest A contiguous range into which the data should be read.
*/
template<std::ranges::contiguous_range range>
void get(range& dest) {
const auto read_size = dest.size() * sizeof(range::value_type);
STREAM_READ_BOUNDS_CHECK(read_size, void());
buffer_.read(dest.data(), read_size);
}
/**
* @brief Skip over count bytes
*
* Skips over a number of bytes from the stream. This should be used
* if the stream holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(const size_type count) {
STREAM_READ_BOUNDS_CHECK(count, void());
buffer_.skip(count);
}
/**
* @brief Provides a string_view over the stream's data, up to the terminator value.
*
* @param terminator An optional terminating/sentinel value.
*
* @return A string view over data up to the provided terminator.
* An empty string_view if a terminator is not found
*/
std::string_view view(value_type terminator = value_type(0)) requires(contiguous<buf_type>) {
const auto pos = buffer_.find_first_of(terminator);
if(pos == buf_type::npos) {
return {};
}
std::string_view view { reinterpret_cast<char*>(buffer_.read_ptr()), pos };
buffer_.skip(pos + 1);
total_read_ += (pos + 1);
return view;
}
/**
* @brief Provides a span over the specified number of elements in the stream.
*
* @param count The number of elements the span will provide a view over.
*
* @return A span representing a view over the requested number of elements
* in the stream.
*
* @note The stream will error if the stream does not contain the requested amount of data.
*/
template<typename out_type = value_type>
std::span<out_type> span(size_type count) requires(contiguous<buf_type>) {
STREAM_READ_BOUNDS_CHECK(sizeof(out_type) * count, {});
std::span span { reinterpret_cast<out_type*>(buffer_.read_ptr()), count };
buffer_.skip(sizeof(out_type) * count);
return span;
}
/** Misc functions **/
/**
* @brief Determine whether the adaptor supports write seeking.
*
* This is determined at compile-time and does not need to checked at
* run-time.
*
* @return True if write seeking is supported, otherwise false.
*/
constexpr static bool can_write_seek() requires(writeable<buf_type>) {
return std::is_same_v<seeking, supported>;
}
/**
* @brief Performs write seeking within the stream.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const stream_seek direction, const offset_type offset)
requires(seekable<buf_type> && writeable<buf_type>) {
if(direction == stream_seek::sk_stream_absolute) {
buffer_.write_seek(buffer_seek::sk_backward, total_write_ - offset);
} else {
buffer_.write_seek(static_cast<buffer_seek>(direction), offset);
}
}
/**
* @brief Returns the size of the stream.
*
* @return The number of bytes of data available to read within the stream.
*/
size_type size() const {
return buffer_.size();
}
/**
* @brief Whether the stream is empty.
*
* @return Returns true if the stream is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const {
return buffer_.empty();
}
/**
* @return The total number of bytes written to the stream.
*/
size_type total_write() const requires(writeable<buf_type>) {
return total_write_;
}
/**
* @return Pointer to stream's underlying buffer.
*/
const buf_type* buffer() const {
return &buffer_;
}
/**
* @return Pointer to stream's underlying buffer.
*/
buf_type* buffer() {
return &buffer_;
}
/**
* @return The stream's state.
*/
stream_state state() const {
return state_;
}
/**
* @return The total number of bytes read from the stream.
*/
size_type total_read() const {
return total_read_;
}
/**
* @return If provided to the constructor, the upper limit on how much data
* can be read from this stream before an error is triggers.
*/
size_type read_limit() const {
return read_limit_;
}
/**
* @brief Determine whether the stream is in an error state.
*
* @return True if no errors occurred on this stream.
*/
bool good() const {
return state_ == stream_state::ok;
}
/**
* @brief Clears the reset state of the stream if an error has occurred.
*/
void clear_error_state() {
state_ = stream_state::ok;
}
operator bool() const {
return good();
}
/**
* @brief Set the stream to an error state.
*/
void set_error_state() {
state_ = stream_state::user_defined_err;
}
};
} // hexi
// #include <hexi/buffer_adaptor.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <ranges>
#include <type_traits>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace hexi {
using namespace detail;
template<byte_oriented buf_type, bool space_optimise = true>
requires std::ranges::contiguous_range<buf_type>
class buffer_adaptor final {
public:
using value_type = typename buf_type::value_type;
using size_type = typename buf_type::size_type;
using offset_type = typename buf_type::size_type;
using contiguous = is_contiguous;
using seeking = supported;
static constexpr auto npos { static_cast<size_type>(-1) };
private:
buf_type& buffer_;
size_type read_;
size_type write_;
public:
buffer_adaptor(buf_type& buffer)
: buffer_(buffer),
read_(0),
write_(buffer.size()) {}
buffer_adaptor(buffer_adaptor&& rhs) = delete;
buffer_adaptor& operator=(buffer_adaptor&&) = delete;
buffer_adaptor& operator=(const buffer_adaptor&) = delete;
buffer_adaptor(const buffer_adaptor&) = delete;
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
read(destination, sizeof(T));
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, size_type length) {
assert(destination);
copy(destination, length);
read_ += length;
if constexpr(space_optimise) {
if(read_ == write_) {
read_ = write_ = 0;
}
}
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void copy(T* destination) const {
copy(destination, sizeof(T));
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, size_type length) const {
assert(destination);
assert(!region_overlap(buffer_.data(), buffer_.size(), destination, length));
std::memcpy(destination, read_ptr(), length);
}
/**
* @brief Skip over length bytes.
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(size_type length) {
read_ += length;
if constexpr(space_optimise) {
if(read_ == write_) {
read_ = write_ = 0;
}
}
}
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(const auto& source) requires(has_resize<buf_type>) {
write(source, sizeof(source));
}
/**
* @brief Write provided data to the container.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, size_type length) requires(has_resize<buf_type>) {
assert(source && !region_overlap(source, length, buffer_.data(), buffer_.size()));
const auto min_req_size = write_ + length;
// we don't use std::back_inserter so we can support seeks
if(buffer_.size() < min_req_size) {
if constexpr(has_resize_overwrite<buf_type>) {
buffer_.resize_and_overwrite(min_req_size, [](char*, size_type size) {
return size;
});
} else {
buffer_.resize(min_req_size);
}
}
std::memcpy(write_ptr(), source, length);
write_ += length;
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
size_type find_first_of(value_type val) const {
const auto data = read_ptr();
for(size_type i = 0, j = size(); i < j; ++i) {
if(data[i] == val) {
return i;
}
}
return npos;
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the container.
*/
size_type size() const {
return buffer_.size() - read_;
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const {
return read_ == write_;
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
value_type& operator[](const size_type index) {
return read_ptr()[index];
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const value_type& operator[](const size_type index) const {
return read_ptr()[index];
}
/**
* @brief Determine whether the adaptor supports write seeking.
*
* This is determined at compile-time and does not need to checked at
* run-time.
*
* @return True if write seeking is supported, otherwise false.
*/
constexpr static bool can_write_seek() requires(has_resize<buf_type>) {
return std::is_same_v<seeking, supported>;
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const buffer_seek direction, const offset_type offset) requires(has_resize<buf_type>) {
switch(direction) {
case buffer_seek::sk_backward:
write_ -= offset;
break;
case buffer_seek::sk_forward:
write_ += offset;
break;
case buffer_seek::sk_absolute:
write_ = offset;
}
}
/**
* @return Pointer to the data available for reading.
*/
const auto read_ptr() const {
return buffer_.data() + read_;
}
/**
* @return Pointer to the data available for reading.
*/
auto read_ptr() {
return buffer_.data() + read_;
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
const auto write_ptr() const requires(has_resize<buf_type>) {
return buffer_.data() + write_;
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
auto write_ptr() requires(has_resize<buf_type>) {
return buffer_.data() + write_;
}
/**
* @return Pointer to the data available for reading.
*/
const auto data() const {
return buffer_.data() + read_;
}
/**
* @return Pointer to the data available for reading.
*/
auto data() {
return buffer_.data() + read_;
}
/**
* @return Pointer to the underlying storage.
*/
const auto storage() const {
return buffer_.data();
}
/**
* @return Pointer to the underlying storage.
*/
auto storage() {
return buffer_.data();
}
/**
* @brief Advances the write cursor.
*
* @param size The number of bytes by which to advance the write cursor.
*/
void advance_write(size_type bytes) {
assert(buffer_.size() >= (write_ + bytes));
write_ += bytes;
}
};
} // hexi
// #include <hexi/buffer_sequence.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#if defined HEXI_WITH_ASIO || defined HEXI_WITH_BOOST_ASIO
#ifdef HEXI_WITH_ASIO
#include <asio/buffer.hpp>
#elif defined HEXI_WITH_BOOST_ASIO
#include <boost/asio/buffer.hpp>
#endif
#ifdef HEXI_BUFFER_DEBUG
#include <span>
#endif
namespace hexi {
#ifdef HEXI_WITH_BOOST_ASIO
namespace asio = boost::asio;
#endif
template<typename buffer_type>
class buffer_sequence {
const buffer_type& buffer_;
public:
buffer_sequence(const buffer_type& buffer)
: buffer_(buffer) { }
class const_iterator {
using node = typename buffer_type::node_type;
public:
const_iterator(const buffer_type& buffer, const node* curr_node)
: buffer_(buffer),
curr_node_(curr_node) {}
const_iterator& operator++() {
curr_node_ = curr_node_->next;
return *this;
}
const_iterator operator++(int) {
const_iterator current(*this);
curr_node_ = curr_node_->next;
return current;
}
asio::const_buffer operator*() const {
const auto buffer = buffer_.buffer_from_node(curr_node_);
return buffer->read_data();
}
bool operator==(const const_iterator& rhs) const {
return curr_node_ == rhs.curr_node_;
}
bool operator!=(const const_iterator& rhs) const {
return curr_node_ != rhs.curr_node_;
}
const_iterator& operator=(const_iterator&) = delete;
#ifdef HEXI_BUFFER_DEBUG
std::span<const char> get_buffer() {
auto buffer = buffer_.buffer_from_node(curr_node_);
return {
reinterpret_cast<const char*>(buffer->read_ptr()), buffer->size()
};
}
#endif
private:
const buffer_type& buffer_;
const node* curr_node_;
};
const_iterator begin() const {
return const_iterator(buffer_, buffer_.root_.next);
}
const_iterator end() const {
return const_iterator(buffer_, &buffer_.root_);
}
friend class const_iterator;
};
} // hexi
#endif // #if defined HEXI_WITH_ASIO || defined HEXI_WITH_BOOST_ASIO
// #include <hexi/concepts.h>
// #include <hexi/dynamic_buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_read.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_base.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#include <cstddef>
namespace hexi::pmc {
class buffer_base {
public:
virtual std::size_t size() const = 0;
virtual bool empty() const = 0;
virtual ~buffer_base() = default;
};
} // pmc, hexi
#include <cstddef>
namespace hexi::pmc {
class buffer_read : virtual public buffer_base {
public:
using value_type = std::byte;
static constexpr auto npos { static_cast<std::size_t>(-1) };
virtual ~buffer_read() = default;
virtual void read(void* destination, std::size_t length) = 0;
virtual void copy(void* destination, std::size_t length) const = 0;
virtual void skip(std::size_t length) = 0;
virtual const std::byte& operator[](const std::size_t index) const = 0;
virtual std::size_t find_first_of(std::byte val) const = 0;
};
} // pmc, hexi
// #include <hexi/pmc/buffer_write.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_base.h>
// #include <hexi/shared.h>
#include <cstddef>
namespace hexi::pmc {
class buffer_write : virtual public buffer_base {
public:
using value_type = std::byte;
virtual ~buffer_write() = default;
virtual void write(const void* source, std::size_t length) = 0;
virtual void reserve(std::size_t length) = 0;
virtual bool can_write_seek() const = 0;
virtual void write_seek(buffer_seek direction, std::size_t offset) = 0;
};
} // pmc, hexi
namespace hexi::pmc {
class buffer : public buffer_read, public buffer_write {
public:
using value_type = std::byte;
using buffer_read::operator[];
virtual std::byte& operator[](const std::size_t index) = 0;
virtual ~buffer() = default;
};
} // pmc, hexi
// #include <hexi/shared.h>
// #include <hexi/allocators/default_allocator.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#include <utility>
namespace hexi {
template<typename T>
struct default_allocator final {
template<typename ...Args>
[[nodiscard]] inline T* allocate(Args&&... args) const {
return new T(std::forward<Args>(args)...);
}
inline void deallocate(T* t) const {
delete t;
}
};
} // hexi
// #include <hexi/detail/intrusive_storage.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#if _MSC_VER
#pragma warning(disable : 4996)
#endif
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <array>
#include <concepts>
#include <span>
#include <type_traits>
#include <cassert>
#include <cstring>
#include <cstddef>
namespace hexi::detail {
struct intrusive_node {
intrusive_node* next;
intrusive_node* prev;
};
template<std::size_t block_size, byte_type storage_type = std::byte>
struct intrusive_storage final {
using value_type = storage_type;
using offset_type = std::remove_const_t<decltype(block_size)>;
offset_type read_offset = 0;
offset_type write_offset = 0;
intrusive_node node {};
std::array<value_type, block_size> storage;
/**
* @brief Clear the container.
*
* Resets the read and write offsets, does not explicitly
* erase the data contained within the buffer but should
* be treated as such.
*/
void clear() {
read_offset = 0;
write_offset = 0;
}
/**
* @brief Write provided data to the container.
*
* If the container size is lower than requested number of bytes,
* the request will be capped at the number of bytes available.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*
* @return The number of bytes copied, which may be less than requested.
*/
std::size_t write(const auto source, std::size_t length) {
assert(!region_overlap(source, length, storage.data(), storage.size()));
std::size_t write_len = block_size - write_offset;
if(write_len > length) {
write_len = length;
}
std::memcpy(storage.data() + write_offset, source, write_len);
write_offset += static_cast<offset_type>(write_len);
return write_len;
}
/**
* @brief Copies a number of bytes to the provided buffer but without
* advancing the read cursor.
*
* If the container size is lower than requested number of bytes,
* the request will be capped at the number of bytes available.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*
* @return The number of bytes copied, which may be less than requested.
*/
std::size_t copy(auto destination, const std::size_t length) const {
assert(!region_overlap(storage.data(), storage.size(), destination, length));
std::size_t read_len = block_size - read_offset;
if(read_len > length) {
read_len = length;
}
std::memcpy(destination, storage.data() + read_offset, read_len);
return read_len;
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* If the container size is lower than requested number of bytes,
* the request will be capped at the number of bytes available.
*
* @param length The number of bytes to skip.
* @param destination The buffer to copy the data to.
* @param allow_optimise Whether the buffer can reuse its space where possible.
*
* @return The number of bytes read, which may be less than requested.
*/
std::size_t read(auto destination, const std::size_t length, const bool allow_optimise = false) {
std::size_t read_len = copy(destination, length);
read_offset += static_cast<offset_type>(read_len);
if(read_offset == write_offset && allow_optimise) {
clear();
}
return read_len;
}
/**
* @brief Skip over requested number of bytes.
*
* Skips over a number of bytes in the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* If the container size is lower than requested number of bytes,
* the request will be capped at the number of bytes available.
*
* @param length The number of bytes to skip.
* @param allow_optimise Whether the buffer can reuse its space where possible.
*
* @return The number of bytes skipped, which may be less than requested.
*/
std::size_t skip(const std::size_t length, const bool allow_optimise = false) {
std::size_t skip_len = block_size - read_offset;
if(skip_len > length) {
skip_len = length;
}
read_offset += static_cast<offset_type>(skip_len);
if(read_offset == write_offset && allow_optimise) {
clear();
}
return skip_len;
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes available to read within the container.
*/
std::size_t size() const {
return write_offset - read_offset;
}
/**
* @brief The amount of free space.
*
* @return The number of bytes of free space within the container.
*/
std::size_t free() const {
return block_size - write_offset;
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const buffer_seek direction, const std::size_t offset) {
switch(direction) {
case buffer_seek::sk_absolute:
write_offset = offset;
break;
case buffer_seek::sk_backward:
write_offset -= static_cast<offset_type>(offset);
break;
case buffer_seek::sk_forward:
write_offset += static_cast<offset_type>(offset);
break;
}
}
/**
* @brief Advances the write cursor.
*
* If the requested number of bytes exceeds the free space, the
* request will be capped at the amount of free space.
*
* @param size The number of bytes by which to advance the write cursor.
*
* @return The number of bytes the write cursor was advanced by, which
* may be less than requested.
*/
std::size_t advance_write(std::size_t size) {
const auto remaining = free();
if(remaining < size) {
size = remaining;
}
write_offset += static_cast<offset_type>(size);
return size;
}
/**
* @brief Retrieve a pointer to the readable portion of the buffer.
*
* @return Pointer to the readable portion of the buffer.
*/
const value_type* read_ptr() const {
return storage.data() + read_offset;
}
/**
* @brief Retrieve a pointer to the readable portion of the buffer.
*
* @return Pointer to the readable portion of the buffer.
*/
value_type* read_ptr() {
return storage.data() + read_offset;
}
/**
* @brief Retrieve a pointer to the writeable portion of the buffer.
*
* @return Pointer to the writeable portion of the buffer.
*/
const value_type* write_ptr () const {
return storage.data() + write_offset;
}
/**
* @brief Retrieve a pointer to the writeable portion of the buffer.
*
* @return Pointer to the writeable portion of the buffer.
*/
value_type* write_ptr() {
return storage.data() + write_offset;
}
/**
* @brief Retrieve a span to the readable portion of the buffer.
*
* @return Span to the readable portion of the buffer.
*/
std::span<const value_type> read_data() const {
return { storage.data() + read_offset, size() };
}
/**
* @brief Retrieve a span to the readable portion of the buffer.
*
* @return Span to the readable portion of the buffer.
*/
std::span<value_type> read_data() {
return { storage.data() + read_offset, size() } ;
}
/**
* @brief Retrieve a span to the writeable portion of the buffer.
*
* @return Span to the writeable portion of the buffer.
*/
std::span<const value_type> write_data() const {
return { storage.data() + write_offset, free() } ;
}
/**
* @brief Retrieve a span to the writeable portion of the buffer.
*
* @return Span to the writeable portion of the buffer.
*/
std::span<value_type> write_data() {
return { storage.data() + write_offset, free() } ;
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
value_type& operator[](const std::size_t index) {
return *(storage.data() + index);
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const value_type& operator[](const std::size_t index) const {
return *(storage.data() + index);
}
};
} // detail, hexi
#include <concepts>
#include <functional>
#include <memory>
#include <utility>
#ifdef HEXI_BUFFER_DEBUG
#include <algorithm>
#include <vector>
#endif
#include <cstddef>
#include <cstdint>
#include <cassert>
namespace hexi {
using namespace detail;
template<typename buffer_type>
class buffer_sequence;
template<decltype(auto) block_sz>
concept int_gt_zero = std::integral<decltype(block_sz)> && block_sz > 0;
template<decltype(auto) block_sz,
byte_type storage_value_type = std::byte,
typename allocator = default_allocator<detail::intrusive_storage<block_sz, storage_value_type>>
>
requires int_gt_zero<block_sz>
class dynamic_buffer final : public pmc::buffer {
public:
using storage_type = intrusive_storage<block_sz, storage_value_type>;
using value_type = storage_value_type;
using node_type = intrusive_node;
using size_type = std::size_t;
using offset_type = std::size_t;
using contiguous = is_non_contiguous;
using seeking = supported;
static constexpr auto npos { static_cast<size_type>(-1) };
using unique_storage = std::unique_ptr<storage_type, std::function<void(storage_type*)>>;
private:
intrusive_node root_;
size_type size_;
[[no_unique_address]] allocator allocator_;
void link_tail_node(intrusive_node* node) {
node->next = &root_;
node->prev = root_.prev;
root_.prev = root_.prev->next = node;
}
void unlink_node(intrusive_node* node) {
node->next->prev = node->prev;
node->prev->next = node->next;
}
inline storage_type* buffer_from_node(const intrusive_node* node) const {
return reinterpret_cast<storage_type*>(std::uintptr_t(node)
- offsetof(storage_type, node));
}
void move(dynamic_buffer& rhs) noexcept {
if(this == &rhs) { // self-assignment
return;
}
clear(); // clear our current blocks rather than swapping them
size_ = rhs.size_;
root_ = rhs.root_;
root_.next->prev = &root_;
root_.prev->next = &root_;
rhs.size_ = 0;
rhs.root_.next = &rhs.root_;
rhs.root_.prev = &rhs.root_;
}
void copy(const dynamic_buffer& rhs) {
if(this == &rhs) { // self-assignment
return;
}
const intrusive_node* head = rhs.root_.next;
root_.next = &root_;
root_.prev = &root_;
size_ = 0;
while(head != &rhs.root_) {
auto buffer = allocate();
*buffer = *buffer_from_node(head);
link_tail_node(&buffer->node);
size_ += buffer->write_offset;
head = head->next;
}
}
#ifdef HEXI_BUFFER_DEBUG
void offset_buffers(std::vector<storage_type*>& buffers, size_type offset) {
std::erase_if(buffers, [&](auto block) {
if(block->size() > offset) {
block->read_offset += offset;
block->write_offset -= offset;
return false;
} else {
return true;
}
});
}
#endif
value_type& byte_at_index(const size_type index) const {
assert(index < size_ && "buffer subscript index out of range");
auto head = root_.next;
auto buffer = buffer_from_node(head);
const auto offset_index = index + buffer->read_offset;
const auto node_index = offset_index / block_sz;
for(size_type i = 0; i < node_index; ++i) {
head = head->next;
}
buffer = buffer_from_node(head);
return (*buffer)[offset_index % block_sz];
}
size_type abs_seek_offset(size_type offset) {
if(offset < size_) {
return size_ - offset;
} else if(offset > size_) {
return offset - size_;
} else {
return 0;
}
}
[[nodiscard]] storage_type* allocate() {
return allocator_.allocate();
}
void deallocate(storage_type* buffer) {
allocator_.deallocate(buffer);
}
public:
dynamic_buffer()
: root_{ .next = &root_, .prev = &root_ },
size_(0) {}
~dynamic_buffer() {
clear();
}
dynamic_buffer& operator=(dynamic_buffer&& rhs) noexcept {
move(rhs);
return *this;
}
dynamic_buffer(dynamic_buffer&& rhs) noexcept {
move(rhs);
}
dynamic_buffer(const dynamic_buffer& rhs) {
copy(rhs);
}
dynamic_buffer& operator=(const dynamic_buffer& rhs) {
clear();
copy(rhs);
return *this;
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
read(destination, sizeof(T));
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, size_type length) override {
assert(length <= size_ && "Chained buffer read too large!");
size_type remaining = length;
while(true) {
auto buffer = buffer_from_node(root_.next);
remaining -= buffer->read(
static_cast<value_type*>(destination) + length - remaining, remaining,
root_.next == root_.prev
);
if(remaining) [[unlikely]] {
unlink_node(root_.next);
deallocate(buffer);
} else {
break;
}
}
size_ -= length;
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the container's read cursor.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void copy(T* destination) const {
copy(destination, sizeof(T));
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the container's read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, const size_type length) const override {
assert(length <= size_ && "Chained buffer copy too large!");
size_type remaining = length;
auto head = root_.next;
while(true) {
const auto buffer = buffer_from_node(head);
remaining -= buffer->copy(
static_cast<value_type*>(destination) + length - remaining, remaining
);
if(remaining) [[unlikely]] {
head = head->next;
} else {
break;
}
}
}
#ifdef HEXI_BUFFER_DEBUG
std::vector<storage_type*> fetch_buffers(const size_type length, const size_type offset = 0) {
size_type total = length + offset;
assert(total <= size_ && "Chained buffer fetch too large!");
std::vector<storage_type*> buffers;
auto head = root_.next;
while(total) {
auto buffer = buffer_from_node(head);
size_type read_size = block_sz - buffer->read_offset;
// guard against overflow - buffer may have more content than requested
if(read_size > total) {
read_size = total;
}
buffers.emplace_back(buffer);
total -= read_size;
head = head->next;
}
if(offset) {
offset_buffers(buffers, offset);
}
return buffers;
}
#endif
/**
* @brief Skip the requested number of bytes.
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(const size_type length) override {
assert(length <= size_ && "Chained buffer skip too large!");
size_type remaining = length;
while(true) {
auto buffer = buffer_from_node(root_.next);
remaining -= buffer->skip(remaining, root_.next == root_.prev);
if(remaining) [[unlikely]] {
unlink_node(root_.next);
deallocate(buffer);
} else {
break;
}
}
size_ -= length;
}
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(const auto& source) {
write(&source, sizeof(source));
}
/**
* @brief Write provided data to the container.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, const size_type length) override {
size_type remaining = length;
intrusive_node* tail = root_.prev;
do {
storage_type* buffer;
if(tail != &root_) [[likely]] {
buffer = buffer_from_node(tail);
} else {
buffer = allocate();
link_tail_node(&buffer->node);
tail = root_.prev;
}
remaining -= buffer->write(
static_cast<const value_type*>(source) + length - remaining, remaining
);
tail = tail->next;
} while(remaining);
size_ += length;
}
/**
* @brief Reserves a number of bytes within the container for future use.
*
* @param length The number of bytes that the container should reserve.
*/
void reserve(const size_type length) override {
size_type remaining = length;
intrusive_node* tail = root_.prev;
do {
storage_type* buffer;
if(tail == &root_) [[unlikely]] {
buffer = allocate();
link_tail_node(&buffer->node);
tail = root_.prev;
} else {
buffer = buffer_from_node(tail);
}
remaining -= buffer->advance_write(remaining);
tail = tail->next;
} while(remaining);
size_ += length;
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the container.
*/
size_type size() const override {
return size_;
}
/**
* @brief Retrieves the last buffer in the container's list.
*
* @return A reference to the value at the specified index.
* @note The container retains ownership over the buffer, so it must not
* be deallocated by the caller.
*/
storage_type* back() const {
if(root_.prev == &root_) {
return nullptr;
}
return buffer_from_node(root_.prev);
}
/**
* @brief Retrieves the buffer's block sized.
*
* @param index The index within the buffer.
* @return A reference to the value at the specified index.
*/
storage_type* front() const {
if(root_.next == &root_) {
return nullptr;
}
return buffer_from_node(root_.next);
}
/**
* @brief Removes the first buffer from the container.
*
* @return A pointer to the popped buffer.
*
* @note Removing the buffer from the container also transfers ownership.
* Therefore, the caller assumes responsibility for deallocating the buffer
* when it is no longer required. Alternatively, it can be pushed back into
* the container to transfer ownership back.
*/
auto pop_front() {
auto buffer = buffer_from_node(root_.next);
size_ -= buffer->size();
unlink_node(root_.next);
return unique_storage(buffer, [&](auto ptr) {
deallocate(ptr);
});
}
/**
* @brief Attaches additional storage to the container.
*
* @param buffer The block to add to the container.
*
* @note Once pushed, the container is assumed to have ownership over the buffer.
* The buffer storage must have been allocated by the same allocator as the container.
*/
void push_back(storage_type* buffer) {
link_tail_node(&buffer->node);
size_ += buffer->write_offset;
}
/**
* @brief Advances the write cursor.
*
* @param size The number of bytes by which the write cursor to advance the cursor.
*/
void advance_write(const size_type size) {
auto buffer = buffer_from_node(root_.prev);
const auto actual = buffer->advance_write(size);
assert(size <= block_sz && actual <= size &&
"Attempted to advance write cursor out of bounds!");
size_ += size;
}
/**
* @brief Determine whether the adaptor supports write seeking.
*
* This is determined at compile-time and does not need to checked at
* run-time.
*
* @return True if write seeking is supported, otherwise false.
*/
bool can_write_seek() const override {
return std::is_same_v<seeking, supported>;
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const buffer_seek direction, size_type offset) override {
// nothing to do in this case
if(direction == buffer_seek::sk_absolute && offset == size_) {
return;
}
auto tail = root_.prev;
switch(direction) {
case buffer_seek::sk_backward:
size_ -= offset;
break;
case buffer_seek::sk_forward:
size_ += offset;
break;
case buffer_seek::sk_absolute:
size_ = offset;
offset = abs_seek_offset(offset);
break;
}
const bool rewind
= (direction == buffer_seek::sk_backward
|| (direction == buffer_seek::sk_absolute && offset < size_));
while(offset) {
auto buffer = buffer_from_node(tail);
const auto max_seek = rewind? buffer->size() : buffer->free();
if(max_seek >= offset) {
buffer->write_seek(direction, offset);
offset = 0;
} else {
buffer->write_seek(direction, max_seek);
offset -= max_seek;
tail = rewind? tail->prev : tail->next;
}
}
root_.prev = tail;
}
/**
* @brief Clears the container.
*/
void clear() {
intrusive_node* head = root_.next;
while(head != &root_) {
auto next = head->next;
deallocate(buffer_from_node(head));
head = next;
}
root_.next = &root_;
root_.prev = &root_;
size_ = 0;
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const override {
return !size_;
}
/**
* @brief Retrieves the container's block size.
*
* @return The block size.
*/
consteval static size_type block_size() {
return block_sz;
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
value_type& operator[](const size_type index) override {
return byte_at_index(index);
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const value_type& operator[](const size_type index) const override {
return byte_at_index(index);
}
/**
* @brief Retrieves the number of allocated blocks currently being used by the container.
*
* @return The number of allocated blocks.
*/
size_type block_count() {
auto node = &root_;
size_type count = 0;
// not calculating based on block size & size as it
// wouldn't play nice with seeking or manual push/pop
while(node->next != root_.prev->next) {
++count;
node = node->next;
}
return count;
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
size_type find_first_of(value_type value) const override {
size_type index = 0;
auto head = root_.next;
while(head != &root_) {
const auto buffer = buffer_from_node(head);
const auto data = buffer->read_data();
for(const auto& byte : data) {
if(byte == value) {
return index;
}
++index;
}
head = head->next;
}
return npos;
}
/**
* @brief Retrieves the container's allocator.
*
* @return The memory allocator.
*/
auto& get_allocator() {
return allocator_;
}
/**
* @brief Retrieves the container's allocator.
*
* @return The memory allocator.
*/
const auto& get_allocator() const {
return allocator_;
}
template<typename buffer_type>
friend class buffer_sequence;
};
} // hexi
// #include <hexi/dynamic_tls_buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/dynamic_buffer.h>
// #include <hexi/allocators/tls_block_allocator.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/allocators/block_allocator.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
#include <array>
#include <memory>
#include <new>
#include <thread>
#include <type_traits>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstdint>
#ifndef NDEBUG
#define HEXI_DEBUG_ALLOCATORS
#endif
namespace hexi {
namespace detail {
struct free_block {
free_block* next;
};
template<decltype(auto) size>
concept gt_zero = size > 0;
template<typename T, typename U>
concept sizeof_gte = sizeof(T) >= sizeof(U);
} // detail
using namespace detail;
struct no_validate_dealloc {};
struct validate_dealloc : no_validate_dealloc {};
/**
* Basic fixed-size block stack allocator that preallocates a slab of memory
* capable of holding a compile-time determined number of elements.
* When constructed, a linked list of chunks is built within the slab and
* each allocation request will take the head node. Since the allocations
* are fixed-size, the list does not need to be traversed for a suitable
* size. Deallocations place the chunk as the new head (LIFO).
*
* If the preallocated slab runs out of chunks, it will fall back to using the
* system allocator rather than allocating additional slabs. This means sizing
* the initial allocation correctly is important for maximum performance, so
* it's better to be pessimistic. This is a server application and RAM is cheap. :)
*
* PagePolicy: 'lock' requests that the OS does not page out the memory slab to disk.
*
* ThreadPolicy: 'same_thread' triggers an assert if an allocated object
* is deallocated from a different thread. Used by the TLS allocator, since
* implementing the functionality there is messier (and slower).
*/
template<typename _ty,
std::size_t _elements,
std::derived_from<no_validate_dealloc> ValidatePolicy = no_validate_dealloc>
requires gt_zero<_elements> && sizeof_gte<_ty, free_block>
class block_allocator {
using tid_type = std::conditional_t<
std::is_same_v<ValidatePolicy, validate_dealloc>, std::thread::id, std::monostate
>;
struct mem_block {
_ty obj;
struct {
[[no_unique_address]] tid_type thread_id;
bool using_new;
} meta;
};
static constexpr auto block_size = sizeof(mem_block);
free_block* head_ = nullptr;
[[no_unique_address]] tid_type thread_id_;
std::array<char, block_size * _elements> storage_;
void initialise_free_list() {
auto storage = storage_.data();
for(std::size_t i = 0; i < _elements; ++i) {
auto block = reinterpret_cast<free_block*>(storage + (block_size * i));
push(block);
}
}
inline void push(free_block* block) {
assert(block);
block->next = head_;
head_ = block;
}
[[nodiscard]] inline free_block* pop() {
if(!head_) {
return nullptr;
}
auto block = head_;
head_ = block->next;
return block;
}
public:
#ifdef HEXI_DEBUG_ALLOCATORS
std::size_t storage_active_count = 0;
std::size_t new_active_count = 0;
std::size_t active_count = 0;
std::size_t total_allocs = 0;
std::size_t total_deallocs = 0;
#endif
block_allocator() requires std::same_as<ValidatePolicy, validate_dealloc>
: thread_id_(std::this_thread::get_id()) {
initialise_free_list();
}
block_allocator() {
initialise_free_list();
}
template<typename ...Args>
[[nodiscard]] inline _ty* allocate(Args&&... args) {
auto block = reinterpret_cast<mem_block*>(pop());
if(block) [[likely]] {
#ifdef HEXI_DEBUG_ALLOCATORS
++storage_active_count;
#endif
block->meta.using_new = false;
} else {
#ifdef HEXI_DEBUG_ALLOCATORS
++new_active_count;
#endif
block = new mem_block;
block->meta.using_new = true;
}
if constexpr(std::is_same_v<ValidatePolicy, validate_dealloc>) {
block->meta.thread_id = thread_id_;
}
#ifdef HEXI_DEBUG_ALLOCATORS
++total_allocs;
++active_count;
#endif
return new (&block->obj) _ty(std::forward<Args>(args)...);
}
inline void deallocate(_ty* t) {
assert(t);
auto block = reinterpret_cast<mem_block*>(t);
if constexpr(std::is_same_v<ValidatePolicy, validate_dealloc>) {
assert(block->meta.thread_id == thread_id_
&& "thread policy violation or clobbered block");
}
if(block->meta.using_new) [[unlikely]] {
#ifdef HEXI_DEBUG_ALLOCATORS
--new_active_count;
#endif
t->~_ty();
delete block;
} else {
#ifdef HEXI_DEBUG_ALLOCATORS
--storage_active_count;
#endif
t->~_ty();
push(reinterpret_cast<free_block*>(t));
}
#ifdef HEXI_DEBUG_ALLOCATORS
++total_deallocs;
--active_count;
#endif
}
~block_allocator() {
#ifdef HEXI_DEBUG_ALLOCATORS
assert(active_count == 0);
#endif
}
};
} // hexi
#include <type_traits>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstdint>
#ifndef NDEBUG
#define HEXI_DEBUG_ALLOCATORS
#endif
namespace hexi {
struct safe_entrant {};
struct no_ref_counting {};
struct unsafe_entrant : safe_entrant {};
struct ref_counting : no_ref_counting {};
template<typename _ty,
std::size_t _elements,
std::derived_from<no_ref_counting> ref_count_policy = no_ref_counting,
std::derived_from<safe_entrant> entrant_policy = safe_entrant
>
class tls_block_allocator final {
using allocator_type = block_allocator<_ty, _elements>;
using ref_count = std::conditional_t<
std::is_same_v<ref_count_policy, ref_counting>, int, std::monostate
>;
using tls_handle_cache = std::conditional_t<
std::is_same_v<entrant_policy, unsafe_entrant>, allocator_type*, std::monostate
>;
static inline thread_local std::unique_ptr<allocator_type> allocator_;
static inline thread_local ref_count ref_count_{};
[[no_unique_address]] tls_handle_cache cached_handle_{};
// Compiler will optimise calls to this out when using unsafe_entrant
inline void initialise() {
if constexpr(std::is_same_v<entrant_policy, safe_entrant>) {
if(!allocator_) {
allocator_ = std::make_unique<allocator_type>();
}
}
}
inline allocator_type* allocator_handle() {
if constexpr(std::is_same_v<entrant_policy, unsafe_entrant>) {
return cached_handle_;
} else {
return allocator_.get();
}
}
public:
#ifdef HEXI_DEBUG_ALLOCATORS
std::size_t total_allocs = 0;
std::size_t total_deallocs = 0;
std::size_t active_allocs = 0;
#endif
tls_block_allocator() {
thread_enter();
}
/*
* When used in conjunction with unsafe_entrant, allows the owning object
* to be executed on another thread without paying for checks on every
* allocation
*/
inline void thread_enter() {
if(!allocator_) {
allocator_ = std::make_unique<allocator_type>();
}
if constexpr(std::is_same_v<entrant_policy, unsafe_entrant>) {
cached_handle_ = allocator_.get();
}
if constexpr(std::is_same_v<ref_count_policy, ref_counting>) {
++ref_count_;
}
}
inline void thread_exit() {
if constexpr(std::is_same_v<ref_count_policy, ref_counting>) {
assert(ref_count_);
--ref_count_;
if(ref_count_ == 0) {
allocator_.reset();
}
}
}
template<typename ...Args>
[[nodiscard]] inline _ty* allocate(Args&&... args) {
/*
* When safe_entrant is set, need to do this here & in ctor unless
* we can be 100% sure that any object using the allocator is created
* on the same thread that ends up using it.
*/
initialise();
#ifdef HEXI_DEBUG_ALLOCATORS
++total_allocs;
++active_allocs;
#endif
return allocator_handle()->allocate(std::forward<Args>(args)...);
}
inline void deallocate(_ty* t) {
assert(t);
#ifdef HEXI_DEBUG_ALLOCATORS
++total_deallocs;
--active_allocs;
#endif
allocator_handle()->deallocate(t);
}
#ifdef HEXI_DEBUG_ALLOCATORS
auto allocator() {
initialise();
return allocator_handle();
}
#endif
~tls_block_allocator() {
thread_exit();
#ifdef HEXI_DEBUG_ALLOCATORS
assert(active_allocs == 0);
#endif
}
};
} // hexi
#include <cstddef>
namespace hexi {
// dynamic_buffer backed by thread-local storage, meaning every
// instance on the same thread shares the same underlying memory.
// As a rule of thumb, an instance should never be touched by any thread
// other than the one on which it was created, not even if synchronised
// ... unless you're positive it won't result in the allocator being called.
//
// Minimum memory usage is intrusive_storage<block_size> * count.
// Additional blocks are not added if the original is exhausted ('colony' structure),
// so the allocator will fall back to the system allocator instead.
//
// Pros: extremely fast allocation/deallocation for many instances per thread
// Cons: everything else.
//
// TL;DR Do not use unless you know what you're doing.
template<decltype(auto) block_size,
std::size_t count,
typename ref_count_policy = no_ref_counting,
typename entrant_policy = safe_entrant,
typename storage_type = std::byte>
using dynamic_tls_buffer = dynamic_buffer<block_size, storage_type,
tls_block_allocator<typename dynamic_buffer<block_size>::storage_type, count, no_ref_counting, entrant_policy>
>;
} // hexi
// #include <hexi/exception.h>
// #include <hexi/endian.h>
// #include <hexi/file_buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/exception.h>
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <filesystem>
#include <utility>
#include <cstddef>
#include <cstdio>
namespace hexi {
using namespace detail;
class file_buffer final {
public:
using size_type = std::size_t;
using offset_type = long;
using value_type = char;
using contiguous = is_non_contiguous;
using seeking = unsupported;
static constexpr auto npos { static_cast<size_type>(-1) };
private:
FILE* file_ = nullptr;
offset_type read_ = 0;
offset_type write_ = 0;
bool error_ = false;
/**
* @brief Explicitly close the underlying file handle.
*
* This function will be called by the object's destructor
* and does not need to be called explicitly.
*
* @note Idempotent.
*/
void close() {
if(file_) {
std::fclose(file_);
file_ = nullptr;
}
}
public:
file_buffer(const std::filesystem::path& path)
: file_buffer(path.string().c_str()) { }
file_buffer(const char* path) {
file_ = std::fopen(path, "a+b");
if(!file_) {
error_ = true;
return;
}
if(std::fseek(file_, 0, SEEK_END)) {
error_ = true;
return;
}
write_ = std::ftell(file_);
if(write_ == -1) {
error_ = true;
}
}
file_buffer(file_buffer&& rhs) noexcept {
file_ = rhs.file_;
read_ = rhs.read_;
write_ = rhs.write_;
error_ = rhs.error_;
rhs.file_ = nullptr;
}
file_buffer& operator=(file_buffer&& rhs) noexcept {
std::exchange(file_, rhs.file_);
read_ = rhs.read_;
write_ = rhs.write_;
error_ = rhs.error_;
return *this;
}
file_buffer& operator=(const file_buffer&) = delete;
file_buffer(const file_buffer&) = delete;
~file_buffer() {
close();
}
/**
* @brief Flush unwritten data.
*/
void flush() {
std::fflush(file_);
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
read(destination, sizeof(T));
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, size_type length) {
if(error_) {
return;
}
if(std::fseek(file_, read_, 0)) {
error_ = true;
return;
}
if(std::fread(destination, length, 1, file_) != 1) {
error_ = true;
return;
}
read_ += length;
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void copy(T* destination) {
copy(destination, sizeof(T));
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, size_type length) {
if(error_) {
return;
} else if(length > size()) {
error_ = true;
throw buffer_underrun(length, read_, size());
}
if(std::fseek(file_, read_, SEEK_SET)) {
error_ = true;
return;
}
if(std::fread(destination, length, 1, file_) != 1) {
error_ = true;
return;
}
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
size_type find_first_of(value_type val) noexcept {
if(error_) {
return npos;
}
if(std::fseek(file_, read_, SEEK_SET)) {
error_ = true;
return npos;
}
value_type buffer{};
for(std::size_t i = 0u, j = size(); i < j; ++i) {
if(std::fread(&buffer, sizeof(value_type), 1, file_) != 1) {
error_ = true;
return npos;
}
if(buffer == val) {
return i;
}
}
return npos;
}
/**
* @brief Skip a requested number of bytes.
*
* Skips over a number of bytes from the file. This should be used
* if the file holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(const size_type length) {
read_ += length;
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const {
return write_ == read_;
}
/**
* @brief Determine whether the adaptor supports write seeking.
*
* This is determined at compile-time and does not need to checked at
* run-time.
*
* @return True if write seeking is supported, otherwise false.
*/
constexpr static bool can_write_seek() {
return std::is_same_v<seeking, supported>;
}
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(const auto& source) {
write(&source, sizeof(source));
}
/**
* @brief Write provided data to the file.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, size_type length) {
if(error_) {
return;
}
if(std::fseek(file_, write_, SEEK_SET)) {
error_ = true;
return;
}
if(std::fwrite(source, length, 1, file_) != 1) {
error_ = true;
return;
}
write_ += length;
}
/**
* @brief Returns the amount of data available in the file.
*
* @return The number of bytes of data available to read from the file.
*/
size_type size() const {
return static_cast<size_type>(write_) - read_;
}
/**
* @brief Retrieve the file handle.
*
* @return The file handle.
*/
FILE* handle() {
return file_;
}
/**
* @brief Retrieve the file handle.
*
* @return The file handle.
*/
const FILE* handle() const {
return file_;
}
/**
* @return True if an error has occurred during file operations.
*/
bool error() const {
return error_;
}
/**
* @return True if no error has occurred during file operations.
*/
operator bool() const {
return !error();
}
};
} // hexi
// #include <hexi/shared.h>
// #include <hexi/static_buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/exception.h>
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <array>
#include <span>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace hexi {
using namespace detail;
template<byte_type storage_type, std::size_t buf_size>
class static_buffer final {
std::array<storage_type, buf_size> buffer_ = {};
std::size_t read_ = 0;
std::size_t write_ = 0;
public:
using size_type = typename decltype(buffer_)::size_type;
using offset_type = size_type;
using value_type = storage_type;
using contiguous = is_contiguous;
using seeking = supported;
static constexpr auto npos { static_cast<size_type>(-1) };
static_buffer() = default;
template<typename... T>
static_buffer(T&&... vals) : buffer_{ std::forward<T>(vals)... } {
write_ = sizeof... (vals);
}
static_buffer(static_buffer&& rhs) = default;
static_buffer& operator=(static_buffer&&) = default;
static_buffer& operator=(const static_buffer&) = default;
static_buffer(const static_buffer&) = default;
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
read(destination, sizeof(T));
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, size_type length) {
copy(destination, length);
read_ += length;
if(read_ == write_) {
read_ = write_ = 0;
}
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void copy(T* destination) const {
copy(destination, sizeof(T));
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, size_type length) const {
assert(!region_overlap(buffer_.data(), buffer_.size(), destination, length));
if(length > size()) {
throw buffer_underrun(length, read_, size());
}
std::memcpy(destination, read_ptr(), length);
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
size_type find_first_of(value_type val) const noexcept {
const auto data = read_ptr();
for(std::size_t i = 0u, j = size(); i < j; ++i) {
if(data[i] == val) {
return i;
}
}
return npos;
}
/**
* @brief Skip over length bytes.
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(const size_type length) {
read_ += length;
if(read_ == write_) {
read_ = write_ = 0;
}
}
/**
* @brief Advances the write cursor.
*
* @param size The number of bytes by which to advance the write cursor.
*/
void advance_write(size_type bytes) {
assert(free() >= bytes);
write_ += bytes;
}
/**
* @brief Clears the container.
*/
void clear() {
read_ = write_ = 0;
}
/**
* @brief Moves any unread data to the front of the buffer, freeing space at the end.
* If a move is performed, pointers obtained from read/write_ptr() will be invalidated.
*
* If the buffer contains no data available for reading, this function will have no effect.
*
* @return True if additional space was made available.
*/
bool defragment() {
if(read_ == 0) {
return false;
}
write_ = size();
std::memmove(buffer_.data(), read_ptr(), write_);
read_ = 0;
return true;
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
value_type& operator[](const size_type index) {
return read_ptr()[index];
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const value_type& operator[](const size_type index) const {
return read_ptr()[index];
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const {
return write_ == read_;
}
/**
* @return Whether the container is full and cannot be further written to.
*/
bool full() const {
return write_ == capacity();
}
/**
* @brief Determine whether the adaptor supports write seeking.
*
* This is determined at compile-time and does not need to checked at
* run-time.
*
* @return True if write seeking is supported, otherwise false.
*/
constexpr static bool can_write_seek() {
return std::is_same_v<seeking, supported>;
}
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(const auto& source) {
write(&source, sizeof(source));
}
/**
* @brief Write provided data to the container.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, size_type length) {
assert(!region_overlap(source, length, buffer_.data(), buffer_.size()));
if(free() < length) {
throw buffer_overflow(length, write_, free());
}
std::memcpy(write_ptr(), source, length);
write_ += length;
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const buffer_seek direction, const size_type offset) {
switch(direction) {
case buffer_seek::sk_backward:
write_ -= offset;
break;
case buffer_seek::sk_forward:
write_ += offset;
break;
case buffer_seek::sk_absolute:
write_ = offset;
}
}
/**
* @return An iterator to the beginning of data available for reading.
*/
auto begin() {
return buffer_.begin() + read_;
}
/**
* @return An iterator to the beginning of data available for reading.
*/
const auto begin() const {
return buffer_.begin() + read_;
}
/**
* @return An iterator to the end of data available for reading.
*/
auto end() {
return buffer_.begin() + write_;
}
/**
* @return An iterator to the end of data available for reading.
*/
const auto end() const {
return buffer_.begin() + write_;
}
/**
* @brief Overall capacity of the container.
*
* @return The container's total size in bytes.
*/
constexpr static size_type capacity() {
return buf_size;
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the container.
*/
size_type size() const {
return write_ - read_;
}
/**
* @brief The amount of free space.
*
* @return The number of bytes of free space within the container.
*/
size_type free() const {
return buf_size - write_;
}
/**
* @return Pointer to the data available for reading.
*/
const value_type* data() const {
return buffer_.data() + read_;
}
/**
* @return Pointer to the data available for reading.
*/
value_type* data() {
return buffer_.data() + read_;
}
/**
* @return Pointer to the data available for reading.
*/
const value_type* read_ptr() const {
return buffer_.data() + read_;
}
/**
* @return Pointer to the data available for reading.
*/
value_type* read_ptr() {
return buffer_.data() + read_;
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
const value_type* write_ptr() const {
return buffer_.data() + write_;
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
value_type* write_ptr() {
return buffer_.data() + write_;
}
/**
* @return Pointer to the underlying storage.
*/
value_type* storage() {
return buffer_.data();
}
/**
* @return Pointer to the underlying storage.
*/
const value_type* storage() const {
return buffer_.data();
}
/**
* @brief Retrieves a span representing the data available for reading contained within
* underlying storage.
*
* @return A span over the data waiting to be read from the container.
*/
std::span<const value_type> read_span() const {
return { read_ptr(), size() };
}
/**
* @brief Retrieves a span representing the free space within the underlying storage.
*
* @return A span over the container's free space.
*/
std::span<value_type> write_span() {
return { write_ptr(), free() };
}
};
} // hexi
// #include <hexi/allocators/block_allocator.h>
// #include <hexi/allocators/default_allocator.h>
// #include <hexi/allocators/tls_block_allocator.h>
// #include <hexi/detail/intrusive_storage.h>
// #include <hexi/pmc/binary_stream.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/binary_stream_reader.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/stream_base.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_base.h>
// #include <hexi/shared.h>
namespace hexi::pmc {
class stream_base {
buffer_base& buffer_;
stream_state state_;
protected:
void set_state(stream_state state) {
state_ = state;
}
public:
explicit stream_base(buffer_base& buffer)
: buffer_(buffer),
state_(stream_state::ok) { }
std::size_t size() const {
return buffer_.size();
}
[[nodiscard]]
bool empty() const {
return buffer_.empty();
}
stream_state state() const {
return state_;
}
bool good() const {
return state() == stream_state::ok;
}
void clear_state() {
set_state(stream_state::ok);
}
operator bool() const {
return good();
}
void set_error_state() {
set_state(stream_state::user_defined_err);
}
virtual ~stream_base() = default;
};
} // pmc, hexi
// #include <hexi/pmc/buffer_read.h>
// #include <hexi/concepts.h>
// #include <hexi/endian.h>
// #include <hexi/exception.h>
// #include <hexi/shared.h>
#include <algorithm>
#include <ranges>
#include <string>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace hexi::pmc {
using namespace detail;
class binary_stream_reader : virtual public stream_base {
buffer_read& buffer_;
std::size_t total_read_;
const std::size_t read_limit_;
void check_read_bounds(std::size_t read_size) {
if(read_size > buffer_.size()) [[unlikely]] {
set_state(stream_state::buff_limit_err);
throw buffer_underrun(read_size, total_read_, buffer_.size());
}
const auto max_read_remaining = read_limit_ - total_read_;
if(read_limit_ && read_size > max_read_remaining) [[unlikely]] {
set_state(stream_state::read_limit_err);
throw stream_read_limit(read_size, total_read_, read_limit_);
}
total_read_ += read_size;
}
public:
explicit binary_stream_reader(buffer_read& source, std::size_t read_limit = 0)
: stream_base(source),
buffer_(source),
total_read_(0),
read_limit_(read_limit) {}
binary_stream_reader(binary_stream_reader&& rhs) noexcept
: stream_base(rhs),
buffer_(rhs.buffer_),
total_read_(rhs.total_read_),
read_limit_(rhs.read_limit_) {
rhs.total_read_ = static_cast<std::size_t>(-1);
rhs.set_state(stream_state::invalid_stream);
}
binary_stream_reader& operator=(binary_stream_reader&&) = delete;
binary_stream_reader& operator=(const binary_stream_reader&) = delete;
binary_stream_reader(const binary_stream_reader&) = delete;
// terminates when it hits a null byte, empty string if none found
binary_stream_reader& operator>>(std::string& dest) {
check_read_bounds(1); // just to prevent trying to read from an empty buffer
auto pos = buffer_.find_first_of(std::byte(0));
if(pos == buffer_read::npos) {
dest.clear();
return *this;
}
dest.resize_and_overwrite(pos, [&](char* strbuf, std::size_t size) {
total_read_ += size;
buffer_.read(strbuf, size);
return size;
});
buffer_.skip(1); // skip null term
return *this;
}
binary_stream_reader& operator>>(has_shr_override<binary_stream_reader> auto&& data) {
return data.operator>>(*this);
}
template<pod T>
requires (!has_shr_override<T, binary_stream_reader>)
binary_stream_reader& operator>>(T& data) {
check_read_bounds(sizeof(data));
buffer_.read(&data, sizeof(data));
return *this;
}
binary_stream_reader& operator>>(pod auto& data) {
check_read_bounds(sizeof(data));
buffer_.read(&data, sizeof(data));
return *this;
}
/**
* @brief Reads a string from the stream.
*
* @param dest The destination string.
*/
void get(std::string& dest) {
*this >> dest;
}
/**
* @brief Reads a fixed-length string from the stream.
*
* @param dest The destination string.
* @param count The number of bytes to be read.
*/
void get(std::string& dest, std::size_t size) {
check_read_bounds(size);
dest.resize_and_overwrite(size, [&](char* strbuf, std::size_t len) {
buffer_.read(strbuf, len);
return len;
});
}
/**
* @brief Read data from the stream into the provided destination argument.
*
* @param dest The destination buffer.
* @param count The number of bytes to be read into the destination.
*/
template<typename T>
void get(T* dest, std::size_t count) {
assert(dest);
const auto read_size = count * sizeof(T);
check_read_bounds(read_size);
buffer_.read(dest, read_size);
}
/**
* @brief Read data from the stream to the destination represented by the iterators.
*
* @param begin The beginning iterator.
* @param end The end iterator.
*/
template<typename It>
void get(It begin, const It end) {
for(; begin != end; ++begin) {
*this >> *begin;
}
}
/**
* @brief Read data from the stream into the provided destination argument.
*
* @param dest A contiguous range into which the data should be read.
*/
template<std::ranges::contiguous_range range>
void get(range& dest) {
const auto read_size = dest.size() * sizeof(range::value_type);
check_read_bounds(read_size);
buffer_.read(dest.data(), read_size);
}
/**
* @brief Read an arithmetic type from the stream.
*
* @return The arithmetic value.
*/
template<arithmetic T>
T get() {
check_read_bounds(sizeof(T));
T t{};
buffer_.read(&t, sizeof(T));
return t;
}
/**
* @brief Read an arithmetic type from the stream.
*
* @return The arithmetic value.
*/
void get(arithmetic auto& dest) {
check_read_bounds(sizeof(dest));
buffer_.read(&dest, sizeof(dest));
}
/**
* @brief Read an arithmetic type from the stream, allowing for endian
* conversion.
*
* @param The destination for the read value.
*/
template<endian::conversion conversion>
void get(arithmetic auto& dest) {
check_read_bounds(sizeof(dest));
buffer_.read(&dest, sizeof(dest));
dest = endian::convert<conversion>(dest);
}
/**
* @brief Read an arithmetic type from the stream, allowing for endian
* conversion.
*
* @return The arithmetic value.
*/
template<arithmetic T, endian::conversion conversion>
T get() {
check_read_bounds(sizeof(T));
T t{};
buffer_.read(&t, sizeof(T));
return endian::convert<conversion>(t);
}
/** Misc functions **/
/**
* @brief Skip over count bytes
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(std::size_t count) {
check_read_bounds(count);
buffer_.skip(count);
}
/**
* @return The total number of bytes read from the stream.
*/
std::size_t total_read() const {
return total_read_;
}
/**
* @return If provided to the constructor, the upper limit on how much data
* can be read from this stream before an error is triggers.
*/
std::size_t read_limit() const {
return read_limit_;
}
/**
* @return Pointer to stream's underlying buffer.
*/
buffer_read* buffer() const {
return &buffer_;
}
};
} // pmc, hexi
// #include <hexi/pmc/binary_stream_writer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/stream_base.h>
// #include <hexi/pmc/buffer_write.h>
// #include <hexi/concepts.h>
// #include <hexi/endian.h>
// #include <hexi/shared.h>
#include <algorithm>
#include <string>
#include <string_view>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
namespace hexi::pmc {
using namespace detail;
class binary_stream_writer : virtual public stream_base {
buffer_write& buffer_;
std::size_t total_write_;
public:
explicit binary_stream_writer(buffer_write& source)
: stream_base(source),
buffer_(source),
total_write_(0) {}
binary_stream_writer(binary_stream_writer&& rhs) noexcept
: stream_base(rhs),
buffer_(rhs.buffer_),
total_write_(rhs.total_write_) {
rhs.total_write_ = static_cast<std::size_t>(-1);
rhs.set_state(stream_state::invalid_stream);
}
binary_stream_writer& operator=(binary_stream_writer&&) = delete;
binary_stream_writer& operator=(const binary_stream_writer&) = delete;
binary_stream_writer(const binary_stream_writer&) = delete;
binary_stream_writer& operator<<(has_shl_override<binary_stream_writer> auto&& data) {
return data.operator<<(*this);
}
template<pod T>
requires (!has_shl_override<T, binary_stream_writer>)
binary_stream_writer& operator<<(const T& data) {
buffer_.write(&data, sizeof(data));
total_write_ += sizeof(data);
return *this;
}
binary_stream_writer& operator<<(const std::string& data) {
buffer_.write(data.data(), data.size() + 1); // +1 also writes terminator
total_write_ += (data.size() + 1);
return *this;
}
binary_stream_writer& operator<<(const char* data) {
assert(data);
const auto len = std::strlen(data);
buffer_.write(data, len + 1); // include terminator
total_write_ += len + 1;
return *this;
}
binary_stream_writer& operator<<(std::string_view& data) {
buffer_.write(data.data(), data.size());
const char term = '\0';
buffer_.write(&term, sizeof(term));
total_write_ += (data.size() + 1);
return *this;
}
/**
* @brief Writes a contiguous range to the stream.
*
* @param data The contiguous range to be written to the stream.
*/
template<std::ranges::contiguous_range range>
void put(range& data) {
const auto write_size = data.size() * sizeof(range::value_type);
buffer_.write(data.data(), write_size);
total_write_ += write_size;
}
/**
* @brief Writes a the provided value to the stream.
*
* @param data The value to be written to the stream.
*/
void put(const arithmetic auto& data) {
buffer_.write(&data, sizeof(data));
total_write_ += sizeof(data);
}
/**
* @brief Writes data to the stream.
*
* @param data The element to be written to the stream.
*/
template<endian::conversion conversion>
void put(const arithmetic auto& data) {
const auto swapped = endian::convert<conversion>(data);
buffer_.write(&swapped, sizeof(swapped));
total_write_ += sizeof(data);
}
/**
* @brief Writes count elements from the provided buffer to the stream.
*
* @param data Pointer to the buffer from which data will be copied to the stream.
* @param count The number of elements to write.
*/
template<pod T>
void put(const T* data, std::size_t count) {
assert(data);
const auto write_size = count * sizeof(T);
buffer_.write(data, write_size);
total_write_ += write_size;
}
/**
* @brief Writes the data from the iterator range to the stream.
*
* @param begin Iterator to the beginning of the data.
* @param end Iterator to the end of the data.
*/
template<typename It>
void put(It begin, const It end) {
for(auto it = begin; it != end; ++it) {
*this << *it;
}
}
/**
* @brief Allows for writing a provided byte value a specified number of times to
* the stream.
*
* @param The byte value that will fill the specified number of bytes.
*/
template<std::size_t size>
void fill(const std::uint8_t value) {
const auto filled = generate_filled<size>(value);
buffer_.write(filled.data(), filled.size());
total_write_ += size;
}
/** Misc functions **/
/**
* @brief Determines whether this container can write seek.
*
* @return Whether this container is capable of write seeking.
*/
bool can_write_seek() const {
return buffer_.can_write_seek();
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const stream_seek direction, const std::size_t offset) {
if(direction == stream_seek::sk_stream_absolute) {
buffer_.write_seek(buffer_seek::sk_backward, total_write_ - offset);
} else {
buffer_.write_seek(static_cast<buffer_seek>(direction), offset);
}
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the stream.
*/
std::size_t size() const {
return buffer_.size();
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const {
return buffer_.empty();
}
/**
* @return The total number of bytes written to the stream.
*/
std::size_t total_write() const {
return total_write_;
}
/**
* @brief Get a pointer to the buffer.
*
* @return Pointer to the underlying buffer.
*/
buffer_write* buffer() {
return &buffer_;
}
/**
* @brief Get a pointer to the buffer.
*
* @return Pointer to the underlying buffer.
*/
const buffer_write* buffer() const {
return &buffer_;
}
};
} // pmc, hexi
// #include <hexi/pmc/stream_base.h>
// #include <hexi/pmc/buffer.h>
#include <cstddef>
namespace hexi::pmc {
class binary_stream final : public binary_stream_reader, public binary_stream_writer {
public:
explicit binary_stream(hexi::pmc::buffer& source, std::size_t read_limit = 0)
: stream_base(source),
binary_stream_reader(source, read_limit),
binary_stream_writer(source) {}
~binary_stream() override = default;
};
} // pmc, hexi
// #include <hexi/pmc/binary_stream_reader.h>
// #include <hexi/pmc/binary_stream_writer.h>
// #include <hexi/pmc/buffer.h>
// #include <hexi/pmc/buffer_adaptor.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer.h>
// #include <hexi/pmc/buffer_read_adaptor.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_read.h>
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <ranges>
#include <stdexcept>
#include <utility>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace hexi::pmc {
using namespace detail;
template<byte_oriented buf_type>
requires std::ranges::contiguous_range<buf_type>
class buffer_read_adaptor : public buffer_read {
buf_type& buffer_;
std::size_t read_;
public:
buffer_read_adaptor(buf_type& buffer)
: buffer_(buffer),
read_(0) {}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
read(destination, sizeof(T));
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, std::size_t length) override {
assert(destination && !region_overlap(buffer_.data(), buffer_.size(), destination, length));
std::memcpy(destination, buffer_.data() + read_, length);
read_ += length;
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void copy(T* destination) const {
copy(destination, sizeof(T));
}
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, std::size_t length) const override {
assert(destination && !region_overlap(buffer_.data(), buffer_.size(), destination, length));
std::memcpy(destination, buffer_.data() + read_, length);
}
/**
* @brief Skip over requested number of bytes.
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(std::size_t length) override {
read_ += length;
}
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the stream.
*/
std::size_t size() const override {
return buffer_.size() - read_;
}
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const override {
return !(buffer_.size() - read_);
}
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const std::byte& operator[](const std::size_t index) const override {
return reinterpret_cast<const std::byte*>(buffer_.data() + read_)[index];
}
/**
* @return Pointer to the data available for reading.
*/
const auto read_ptr() const {
return buffer_.data() + read_;
}
/**
* @return The current read offset.
*/
const auto read_offset() const {
return read_;
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
std::size_t find_first_of(std::byte val) const override {
for(auto i = read_; i < size(); ++i) {
if(static_cast<std::byte>(buffer_[i]) == val) {
return i - read_;
}
}
return npos;
}
/**
* @brief Clear the underlying buffer and reset state.
*/
void clear() {
read_ = 0;
buffer_.clear();
}
};
} // pmc, hexi
// #include <hexi/pmc/buffer_write_adaptor.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_write.h>
// #include <hexi/shared.h>
// #include <hexi/concepts.h>
#include <ranges>
#include <cassert>
#include <cstddef>
#include <cstring>
namespace hexi::pmc {
using namespace detail;
template<byte_oriented buf_type>
requires std::ranges::contiguous_range<buf_type>
class buffer_write_adaptor : public buffer_write {
buf_type& buffer_;
std::size_t write_;
public:
buffer_write_adaptor(buf_type& buffer)
: buffer_(buffer),
write_(buffer.size()) {}
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(auto& source) {
write(&source, sizeof(source));
}
/**
* @brief Write provided data to the container.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, std::size_t length) override {
assert(source && !region_overlap(source, length, buffer_.data(), buffer_.size()));
const auto min_req_size = write_ + length;
// we don't use std::back_inserter so we can support seeks
if(buffer_.size() < min_req_size) {
if constexpr(has_resize_overwrite<buf_type>) {
buffer_.resize_and_overwrite(min_req_size, [](char*, std::size_t size) {
return size;
});
} else {
buffer_.resize(min_req_size);
}
}
std::memcpy(buffer_.data() + write_, source, length);
write_ += length;
}
/**
* @brief Reserves a number of bytes within the container for future use.
*
* @param length The number of bytes that the container should reserve.
*/
void reserve(const std::size_t length) override {
buffer_.reserve(length);
}
/**
* @brief Determines whether this container can write seek.
*
* @return Whether this container is capable of write seeking.
*/
bool can_write_seek() const override {
return true;
}
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(const buffer_seek direction, const std::size_t offset) override {
switch(direction) {
case buffer_seek::sk_backward:
write_ -= offset;
break;
case buffer_seek::sk_forward:
write_ += offset;
break;
case buffer_seek::sk_absolute:
write_ = offset;
}
}
/**
* @return Pointer to the underlying storage.
*/
const auto storage() const {
return buffer_.data();
}
/**
* @return Pointer to the underlying storage.
*/
auto storage() {
return buffer_.data();
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
auto write_ptr() {
return buffer_.data() + write_;
}
/**
* @return Pointer to the location within the buffer where the next write
* will be made.
*/
const auto write_ptr() const {
return buffer_.data() + write_;
}
/**
* @brief Clear the underlying buffer and reset state.
*/
void clear() {
write_ = 0;
buffer_.clear();
}
};
} // pmc, hexi
// #include <hexi/concepts.h>
#include <ranges>
namespace hexi::pmc {
template<byte_oriented buf_type, bool allow_optimise = true>
requires std::ranges::contiguous_range<buf_type>
class buffer_adaptor final : public buffer_read_adaptor<buf_type>,
public buffer_write_adaptor<buf_type>,
public buffer {
void clear() {
if(buffer_read_adaptor<buf_type>::read_ptr() == buffer_write_adaptor<buf_type>::write_ptr()) {
buffer_read_adaptor<buf_type>::clear();
buffer_write_adaptor<buf_type>::clear();
}
}
public:
explicit buffer_adaptor(buf_type& buffer)
: buffer_read_adaptor<buf_type>(buffer),
buffer_write_adaptor<buf_type>(buffer) {}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
*/
template<typename T>
void read(T* destination) {
buffer_read_adaptor<buf_type>::read(destination);
if constexpr(allow_optimise) {
clear();
}
}
/**
* @brief Reads a number of bytes to the provided buffer.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to read into the buffer.
*/
void read(void* destination, std::size_t length) override {
buffer_read_adaptor<buf_type>::read(destination, length);
if constexpr(allow_optimise) {
clear();
}
};
/**
* @brief Write data to the container.
*
* @param source Pointer to the data to be written.
*/
void write(const auto& source) {
buffer_write_adaptor<buf_type>::write(source);
};
/**
* @brief Write provided data to the container.
*
* @param source Pointer to the data to be written.
* @param length Number of bytes to write from the source.
*/
void write(const void* source, std::size_t length) override {
buffer_write_adaptor<buf_type>::write(source, length);
};
void copy(auto* destination) const {
buffer_read_adaptor<buf_type>::copy(destination);
};
/**
* @brief Copies a number of bytes to the provided buffer but without advancing
* the read cursor.
*
* @param destination The buffer to copy the data to.
* @param length The number of bytes to copy.
*/
void copy(void* destination, std::size_t length) const override {
buffer_read_adaptor<buf_type>::copy(destination, length);
};
/**
* @brief Skip over requested number of bytes.
*
* Skips over a number of bytes from the container. This should be used
* if the container holds data that you don't care about but don't want
* to have to read it to another buffer to move beyond it.
*
* @param length The number of bytes to skip.
*/
void skip(std::size_t length) override {
buffer_read_adaptor<buf_type>::skip(length);
if constexpr(allow_optimise) {
clear();
}
};
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
const std::byte& operator[](const std::size_t index) const override {
return buffer_read_adaptor<buf_type>::operator[](index);
};
/**
* @brief Retrieves a reference to the specified index within the container.
*
* @param index The index within the container.
*
* @return A reference to the value at the specified index.
*/
std::byte& operator[](const std::size_t index) override {
const auto offset = buffer_read_adaptor<buf_type>::read_offset();
auto buffer = buffer_write_adaptor<buf_type>::storage();
return reinterpret_cast<std::byte*>(buffer + offset)[index];
}
/**
* @brief Reserves a number of bytes within the container for future use.
*
* @param length The number of bytes that the container should reserve.
*/
void reserve(std::size_t length) override {
buffer_write_adaptor<buf_type>::reserve(length);
};
/**
* @brief Determines whether this container can write seek.
*
* @return Whether this container is capable of write seeking.
*/
bool can_write_seek() const override {
return buffer_write_adaptor<buf_type>::can_write_seek();
};
/**
* @brief Performs write seeking within the container.
*
* @param direction Specify whether to seek in a given direction or to absolute seek.
* @param offset The offset relative to the seek direction or the absolute value
* when using absolute seeking.
*/
void write_seek(buffer_seek direction, std::size_t offset) override {
buffer_write_adaptor<buf_type>::write_seek(direction, offset);
};
/**
* @brief Returns the size of the container.
*
* @return The number of bytes of data available to read within the stream.
*/
std::size_t size() const override {
return buffer_read_adaptor<buf_type>::size();
};
/**
* @brief Whether the container is empty.
*
* @return Returns true if the container is empty (has no data to be read).
*/
[[nodiscard]]
bool empty() const override {
return buffer_read_adaptor<buf_type>::empty();
}
/**
* @brief Attempts to locate the provided value within the container.
*
* @param value The value to locate.
*
* @return The position of value or npos if not found.
*/
std::size_t find_first_of(std::byte val) const override {
return buffer_read_adaptor<buf_type>::find_first_of(val);
}
};
} // pmc, hexi
// #include <hexi/pmc/buffer_base.h>
// #include <hexi/pmc/buffer_read.h>
// #include <hexi/pmc/buffer_read_adaptor.h>
// #include <hexi/pmc/buffer_write.h>
// #include <hexi/pmc/buffer_write_adaptor.h>
// #include <hexi/pmc/null_buffer.h>
// _ _
// | |__ _____ _(_)
// | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed
// | | | | __/> <| | Version 1.0
// |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi
// #include <hexi/pmc/buffer_write.h>
// #include <hexi/shared.h>
// #include <hexi/exception.h>
#include <cstddef>
namespace hexi::pmc {
class null_buffer final : public buffer_write {
public:
using size_type = std::size_t;
using value_type = std::byte;
using contiguous = is_contiguous;
using seeking = unsupported;
void write(const auto& elem) {}
void write(const void* source, size_type length) override {};
void read(auto* elem) {}
void read(void* destination, size_type length) {};
void copy(auto* elem) const {}
void copy(void* destination, size_type length) const {};
void reserve(const size_type length) override {};
size_type size() const override{ return 0; };
[[nodiscard]] bool empty() const override { return true; };
bool can_write_seek() const override { return false; }
void write_seek(const buffer_seek direction, const std::size_t offset) override {
throw exception("Don't do this on a null_buffer");
};
};
} // pmc, hexi
// #include <hexi/pmc/stream_base.h>
#include <array>
#include <vector>
#include <cstddef>
struct UserPacket {
// uint64_t user_id;
// uint64_t timestamp;
// std::array<uint8_t, 16> ipv6;
std::string test;
};
auto deserialise(std::span<const char> network_buffer) {
hexi::buffer_adaptor adaptor(network_buffer); // wrap the buffer
hexi::binary_stream stream(adaptor); // create a binary stream
// deserialise!
UserPacket packet;
stream >> packet;
return packet;
}
auto serialise(const UserPacket& packet) {
std::vector<uint8_t> buffer;
hexi::buffer_adaptor adaptor(buffer); // wrap the buffer
hexi::binary_stream stream(adaptor); // create a binary stream
// serialise!
stream << packet;
return buffer;
}
int main()
{
UserPacket foo;
auto bar = serialise(foo);
return bar.size();
}