51 : std::invalid_argument(std::move(message) +
" at byte " +
56 std::size_t
byte_offset() const noexcept {
return byte_offset_; }
59 std::size_t byte_offset_;
73 std::span<const std::byte> bytes) {
74 { sink.write(bytes) } -> std::same_as<void>;
75 { sink.write_at(position, bytes) } -> std::same_as<void>;
76 { sink.finish() } -> std::same_as<void>;
88 bool empty() const noexcept {
return data_.empty(); }
91 std::size_t
size_bytes() const noexcept {
return data_.size(); }
94 std::span<const std::byte>
bytes() const noexcept {
return data_; }
101 if (
bytes.size() > data_.max_size() - data_.size()) {
102 throw std::length_error(
"Binary output is too large");
104 data_.insert(data_.end(),
bytes.begin(),
bytes.end());
112 if (position > data_.size() ||
bytes.size() > data_.size() - position) {
113 throw std::out_of_range(
"Binary output patch is outside the output");
115 std::ranges::copy(
bytes, data_.begin() + position);
122 std::vector<std::byte>
take() {
return std::exchange(data_, {}); }
125 std::vector<std::byte> data_;
128static_assert(SeekableBinaryOutputSink<VectorOutputSink>);
142 bool empty() const noexcept {
return size_bytes_ == 0; }
145 std::size_t
size_bytes() const noexcept {
return size_bytes_; }
151 std::span<const std::byte>
bytes() const noexcept {
152 return storage_.first(size_bytes_);
160 if (
bytes.size() > storage_.size() - size_bytes_) {
161 throw std::length_error(
"Fixed binary output capacity exceeded");
163 std::ranges::copy(
bytes, storage_.begin() + size_bytes_);
164 size_bytes_ +=
bytes.size();
172 if (position > size_bytes_ ||
bytes.size() > size_bytes_ - position) {
173 throw std::out_of_range(
"Binary output patch is outside the output");
175 std::ranges::copy(
bytes, storage_.begin() + position);
182 std::span<std::byte> storage_;
183 std::size_t size_bytes_ = 0;
186static_assert(SeekableBinaryOutputSink<SpanOutputSink>);
210 template <SeekableBinaryOutputSink Sink>
213 : owned_buffer_(allocate_buffer(buffer_bytes)),
214 buffer_(owned_buffer_.get(), buffer_bytes) {
226 template <SeekableBinaryOutputSink Sink>
227 BinaryWriter(Sink& sink, std::span<std::byte> buffer) : buffer_(buffer) {
228 if (buffer.empty()) {
229 throw std::invalid_argument(
230 "BinaryWriter staging buffer must be non-empty");
245 return flushed_bytes_ + buffered_bytes_;
252 void write_u8(std::uint8_t value) { write_unsigned(value); }
255 void write_u16(std::uint16_t value) { write_unsigned(value); }
258 void write_u32(std::uint32_t value) { write_unsigned(value); }
261 void write_u64(std::uint64_t value) { write_unsigned(value); }
265 write_unsigned(std::bit_cast<std::uint8_t>(value));
270 write_unsigned(std::bit_cast<std::uint16_t>(value));
275 write_unsigned(std::bit_cast<std::uint32_t>(value));
280 write_unsigned(std::bit_cast<std::uint64_t>(value));
288 if constexpr (
sizeof(std::size_t) >
sizeof(std::uint64_t)) {
289 if (value > std::numeric_limits<std::uint64_t>::max()) {
290 throw std::length_error(
"Size does not fit in the wire format");
293 write_u64(
static_cast<std::uint64_t
>(value));
299 require_output_size(bytes.size());
300 while (!bytes.empty()) {
301 if (buffered_bytes_ != 0) {
302 const std::size_t count =
303 std::min(bytes.size(), buffer_.size() - buffered_bytes_);
304 std::ranges::copy(bytes.first(count),
305 buffer_.begin() + buffered_bytes_);
306 buffered_bytes_ += count;
307 bytes = bytes.subspan(count);
308 if (buffered_bytes_ == buffer_.size()) {
314 if (bytes.size() >= buffer_.size()) {
315 const std::size_t count = bytes.size();
316 write_to_sink(bytes);
317 flushed_bytes_ += count;
321 std::ranges::copy(bytes, buffer_.begin());
322 buffered_bytes_ = bytes.size();
330 require_output_size(count);
332 const std::size_t writable = buffer_.size() - buffered_bytes_;
333 const std::size_t chunk = std::min(count, writable);
334 std::fill_n(buffer_.begin() + buffered_bytes_, chunk, std::byte{0});
335 buffered_bytes_ += chunk;
337 if (buffered_bytes_ == buffer_.size()) {
349 if (alignment == 0) {
350 throw std::invalid_argument(
"Serialization alignment must be non-zero");
352 const std::size_t remainder =
size_bytes() % alignment;
353 if (remainder != 0) {
373 void patch_u64(std::size_t position, std::uint64_t value) {
376 throw std::out_of_range(
"Serialization patch is outside the output");
379 std::array<std::byte,
sizeof(value)> encoded{};
380 for (std::size_t
byte = 0;
byte <
sizeof(value); ++byte) {
381 encoded[byte] =
static_cast<std::byte
>((value >> (
byte * 8)) & 0xffu);
384 if (position >= flushed_bytes_) {
385 const std::size_t buffered_position = position - flushed_bytes_;
386 std::ranges::copy(encoded, buffer_.begin() + buffered_position);
390 if (position + encoded.size() > flushed_bytes_) {
393 write_at_sink(position, encoded);
413 if (state_ == State::kFinished) {
420 state_ = State::kFinished;
422 state_ = State::kFailed;
428 enum class State { kOpen, kFinished, kFailed };
430 using WriteSink = void (*)(
void*, std::span<const std::byte>);
431 using WriteAtSink = void (*)(
void*, std::size_t, std::span<const std::byte>);
432 using FinishSink = void (*)(
void*);
434 static std::unique_ptr<std::byte[]> allocate_buffer(
435 std::size_t buffer_bytes) {
436 if (buffer_bytes == 0) {
437 throw std::invalid_argument(
438 "BinaryWriter staging buffer must be non-empty");
440 return std::make_unique<std::byte[]>(buffer_bytes);
443 template <SeekableBinaryOutputSink Sink>
444 void bind(Sink& sink)
noexcept {
445 sink_ = std::addressof(sink);
446 write_sink_ = [](
void* output, std::span<const std::byte> bytes) {
447 static_cast<Sink*
>(output)->write(bytes);
449 write_at_sink_ = [](
void* output, std::size_t position,
450 std::span<const std::byte> bytes) {
451 static_cast<Sink*
>(output)->write_at(position, bytes);
453 finish_sink_ = [](
void* output) {
static_cast<Sink*
>(output)->
finish(); };
456 void require_open()
const {
457 if (state_ == State::kFinished) {
458 throw std::logic_error(
"BinaryWriter is already finished");
460 if (state_ == State::kFailed) {
461 throw std::logic_error(
"BinaryWriter is unusable after sink failure");
465 void require_output_size(std::size_t count)
const {
466 if (count > std::numeric_limits<std::size_t>::max() -
size_bytes()) {
467 throw std::length_error(
"Serialized output is too large");
471 void flush_buffer() {
472 if (buffered_bytes_ == 0) {
475 const std::size_t count = buffered_bytes_;
476 write_to_sink(buffer_.first(count));
477 flushed_bytes_ += count;
481 void write_to_sink(std::span<const std::byte> bytes) {
483 write_sink_(sink_, bytes);
485 state_ = State::kFailed;
490 void write_at_sink(std::size_t position, std::span<const std::byte> bytes) {
492 write_at_sink_(sink_, position, bytes);
494 state_ = State::kFailed;
499 template <std::
unsigned_
integral T>
500 void write_unsigned(T value) {
501 std::array<std::byte,
sizeof(T)> encoded{};
502 for (std::size_t
byte = 0;
byte <
sizeof(T); ++byte) {
503 encoded[byte] =
static_cast<std::byte
>((value >> (
byte * 8)) & T{0xff});
508 void* sink_ =
nullptr;
509 WriteSink write_sink_ =
nullptr;
510 WriteAtSink write_at_sink_ =
nullptr;
511 FinishSink finish_sink_ =
nullptr;
512 std::unique_ptr<std::byte[]> owned_buffer_;
513 std::span<std::byte> buffer_;
514 std::size_t flushed_bytes_ = 0;
515 std::size_t buffered_bytes_ = 0;
516 State state_ = State::kOpen;
529 explicit BinaryReader(std::span<const std::byte> data) : data_(data) {}
535 std::size_t
size_bytes() const noexcept {
return data_.size(); }
538 std::size_t
position() const noexcept {
return offset_; }
541 std::size_t
byte_offset() const noexcept {
return absolute_position(); }
544 std::size_t
remaining() const noexcept {
return data_.size() - offset_; }
548 return data_.subspan(offset_);
552 std::uint8_t
read_u8() {
return read_unsigned<std::uint8_t>(); }
555 std::uint16_t
read_u16() {
return read_unsigned<std::uint16_t>(); }
558 std::uint32_t
read_u32() {
return read_unsigned<std::uint32_t>(); }
561 std::uint64_t
read_u64() {
return read_unsigned<std::uint64_t>(); }
565 return std::bit_cast<std::int8_t>(read_unsigned<std::uint8_t>());
570 return std::bit_cast<std::int16_t>(read_unsigned<std::uint16_t>());
575 return std::bit_cast<std::int32_t>(read_unsigned<std::uint32_t>());
580 return std::bit_cast<std::int64_t>(read_unsigned<std::uint64_t>());
589 const std::size_t field_offset = candidate.absolute_position();
590 const std::uint64_t encoded = candidate.
read_u64();
591 if (encoded > std::numeric_limits<std::size_t>::max()) {
592 throw std::length_error(
"Serialized size at byte " +
593 std::to_string(field_offset) +
594 " does not fit in size_t");
597 return static_cast<std::size_t
>(encoded);
606 const std::span<const std::byte> result = data_.subspan(offset_, count);
616 const std::size_t origin = absolute_position();
636 absolute_position());
639 while (!candidate.
empty()) {
640 const std::size_t
byte_offset = candidate.absolute_position();
641 if (candidate.
read_u8() != 0) {
650 BinaryReader(std::span<const std::byte> data, std::size_t origin)
651 : data_(data), origin_(origin) {}
653 std::size_t absolute_position() const noexcept {
return origin_ + offset_; }
655 void require(std::size_t count)
const {
657 throw SerializationError(
"Truncated serialized input",
658 absolute_position());
662 template <std::
unsigned_
integral T>
666 for (std::size_t
byte = 0;
byte <
sizeof(T); ++byte) {
668 static_cast<T
>(std::to_integer<std::uint8_t>(data_[offset_ +
byte]))
671 offset_ +=
sizeof(T);
675 std::span<const std::byte> data_;
676 std::size_t offset_ = 0;
677 std::size_t origin_ = 0;
692template <
class Derived>
697 requires requires(
const Derived& value,
BinaryWriter& output) {
698 { value.serialize_impl(output) } -> std::same_as<void>;
701 derived().serialize_impl(writer);
709 template <
class... Context>
711 requires requires(
BinaryReader& input, Context&&... arguments) {
713 Derived::deserialize_impl(input, std::forward<Context>(arguments)...)
714 } -> std::same_as<Derived>;
719 Derived::deserialize_impl(candidate, std::forward<Context>(context)...);
729 template <
class... Context>
731 Context&&... context)
732 requires requires(
BinaryReader& input, Context&&... arguments) {
734 Derived::deserialize_impl(input, std::forward<Context>(arguments)...)
735 } -> std::same_as<Derived>;
739 Derived result =
deserialize(reader, std::forward<Context>(context)...);
740 data = data.subspan(reader.position());
745 const Derived& derived()
const {
return static_cast<const Derived&
>(*this); }
751 { value.serialize(writer) } -> std::same_as<void>;
757template <
class T,
class... Context>
760 T::deserialize(reader, std::forward<Context>(context)...)
761 } -> std::same_as<T>;
Bounds-checked reader for canonical little-endian binary data.
Definition serialization.h:526
std::size_t size_bytes() const noexcept
Return the total number of bytes in this reader.
Definition serialization.h:535
std::int64_t read_i64()
Read a signed little-endian 64-bit integer.
Definition serialization.h:579
std::uint16_t read_u16()
Read an unsigned little-endian 16-bit integer.
Definition serialization.h:555
BinaryReader(std::span< const std::byte > data)
Construct a reader over caller-owned data.
Definition serialization.h:529
void skip(std::size_t count)
Advance by exactly count bytes.
Definition serialization.h:624
std::int8_t read_i8()
Read a signed eight-bit integer.
Definition serialization.h:564
std::size_t remaining() const noexcept
Return the number of unconsumed bytes.
Definition serialization.h:544
std::span< const std::byte > remaining_bytes() const noexcept
Return all currently unconsumed bytes.
Definition serialization.h:547
std::uint8_t read_u8()
Read an unsigned eight-bit integer.
Definition serialization.h:552
BinaryReader read_subreader(std::size_t count)
Read a bounded region as an independent child reader.
Definition serialization.h:615
std::span< const std::byte > read_bytes(std::size_t count)
Read exactly count uninterpreted bytes.
Definition serialization.h:604
std::uint64_t read_u64()
Read an unsigned little-endian 64-bit integer.
Definition serialization.h:561
std::int32_t read_i32()
Read a signed little-endian 32-bit integer.
Definition serialization.h:574
void require_zero_padding(std::size_t maximum)
Consume at most maximum trailing zero-padding bytes.
Definition serialization.h:633
std::uint32_t read_u32()
Read an unsigned little-endian 32-bit integer.
Definition serialization.h:558
bool empty() const noexcept
Return whether all input bytes have been consumed.
Definition serialization.h:532
std::size_t position() const noexcept
Return the number of bytes consumed by this reader.
Definition serialization.h:538
std::size_t byte_offset() const noexcept
Return the current byte offset in the outermost input.
Definition serialization.h:541
std::size_t read_size()
Read an unsigned 64-bit size and convert it to size_t.
Definition serialization.h:587
std::int16_t read_i16()
Read a signed little-endian 16-bit integer.
Definition serialization.h:569
Bounded-buffer writer for canonical little-endian binary data.
Definition serialization.h:198
bool empty() const noexcept
Return whether no bytes have been written.
Definition serialization.h:241
void write_i16(std::int16_t value)
Write a signed 16-bit integer in little-endian order.
Definition serialization.h:269
std::size_t buffer_size_bytes() const noexcept
Return the maximum staging-buffer size.
Definition serialization.h:249
void write_bytes(std::span< const std::byte > bytes)
Append bytes without interpretation.
Definition serialization.h:297
void write_i64(std::int64_t value)
Write a signed 64-bit integer in little-endian order.
Definition serialization.h:279
void write_i32(std::int32_t value)
Write a signed 32-bit integer in little-endian order.
Definition serialization.h:274
BinaryWriter(Sink &sink, std::size_t buffer_bytes=kDefaultBufferBytes)
Construct a writer with an owned, fixed-size staging buffer.
Definition serialization.h:211
void write_u32(std::uint32_t value)
Write an unsigned 32-bit integer in little-endian order.
Definition serialization.h:258
void write_u8(std::uint8_t value)
Write an unsigned eight-bit integer.
Definition serialization.h:252
void finish()
Deliver all bytes and complete the sink.
Definition serialization.h:412
static constexpr std::size_t kDefaultBufferBytes
Default bounded staging-buffer size.
Definition serialization.h:201
BinaryWriter(Sink &sink, std::span< std::byte > buffer)
Construct a writer over a caller-owned staging buffer.
Definition serialization.h:227
std::size_t size_bytes() const noexcept
Return the logical number of bytes written.
Definition serialization.h:244
void align_to(std::size_t alignment)
Pad with zero bytes to the next multiple of alignment.
Definition serialization.h:347
void write_i8(std::int8_t value)
Write a signed eight-bit integer.
Definition serialization.h:264
void write_zeros(std::size_t count)
Append count zero bytes.
Definition serialization.h:328
void write_u16(std::uint16_t value)
Write an unsigned 16-bit integer in little-endian order.
Definition serialization.h:255
void patch_u64(std::size_t position, std::uint64_t value)
Replace an existing 64-bit field with value.
Definition serialization.h:373
std::size_t write_u64_placeholder()
Write a zero 64-bit field and return its byte position.
Definition serialization.h:363
void write_u64(std::uint64_t value)
Write an unsigned 64-bit integer in little-endian order.
Definition serialization.h:261
void write_size(std::size_t value)
Write a platform size as an unsigned 64-bit integer.
Definition serialization.h:287
void flush()
Deliver currently buffered bytes while keeping the writer open.
Definition serialization.h:400
CRTP facade for optional binary serialization and deserialization.
Definition serialization.h:693
static Derived deserialize(std::span< const std::byte > &data, Context &&... context)
Restore exactly Derived and advance a mutable byte span.
Definition serialization.h:730
static Derived deserialize(BinaryReader &reader, Context &&... context)
Restore exactly Derived and advance reader on success.
Definition serialization.h:710
void serialize(BinaryWriter &writer) const
Serialize this value through its concrete implementation.
Definition serialization.h:696
Error raised while decoding malformed serialized data.
Definition serialization.h:45
SerializationError(std::string message, std::size_t byte_offset)
Construct an error for byte_offset in the input artifact.
Definition serialization.h:50
std::size_t byte_offset() const noexcept
Return the byte offset associated with the error.
Definition serialization.h:56
bool empty() const noexcept
Return whether no output bytes have been written.
Definition serialization.h:142
void write_at(std::size_t position, std::span< const std::byte > bytes)
Replace output bytes beginning at position.
Definition serialization.h:171
std::size_t size_bytes() const noexcept
Return the number of output bytes written.
Definition serialization.h:145
std::size_t capacity_bytes() const noexcept
Return the fixed output capacity in bytes.
Definition serialization.h:148
std::span< const std::byte > bytes() const noexcept
Return the prefix written so far.
Definition serialization.h:151
SpanOutputSink(std::span< std::byte > storage)
Construct an empty output over caller-owned storage.
Definition serialization.h:139
void write(std::span< const std::byte > bytes)
Append bytes to the output.
Definition serialization.h:159
void finish() noexcept
Complete this fixed-span output.
Definition serialization.h:179
Explicit owning sink for binary data collected in memory.
Definition serialization.h:85
bool empty() const noexcept
Return whether no output bytes have been written.
Definition serialization.h:88
std::span< const std::byte > bytes() const noexcept
Return a view of all output bytes.
Definition serialization.h:94
void reserve_bytes(std::size_t size_bytes)
Reserve memory for at least size_bytes output bytes.
Definition serialization.h:97
void finish() noexcept
Complete this in-memory output.
Definition serialization.h:119
std::vector< std::byte > take()
Transfer the bytes and reset this sink to an empty state.
Definition serialization.h:122
void write_at(std::size_t position, std::span< const std::byte > bytes)
Replace output bytes beginning at position.
Definition serialization.h:111
std::size_t size_bytes() const noexcept
Return the number of output bytes written.
Definition serialization.h:91
void write(std::span< const std::byte > bytes)
Append bytes to the output.
Definition serialization.h:100
Type restorable exactly from a reader and the listed context types.
Definition serialization.h:758
Contract for an append-only output that supports checked overwrites.
Definition serialization.h:71
Type exposing the common binary serialization operation.
Definition serialization.h:750
DeserializationValidation
Validation strength used while restoring serialized indexes.
Definition serialization.h:28
@ kQuick
Check framing, dimensions, references, and other conditions needed for memory-safe terminating querie...
Definition serialization.h:33
@ kFull
Additionally authenticate all source-derived metadata against the supplied source contents.
Definition serialization.h:39