Pixie
Loading...
Searching...
No Matches
serialization.h
1#pragma once
2
4
5#include <array>
6#include <concepts>
7#include <cstddef>
8#include <cstdint>
9#include <limits>
10#include <span>
11#include <stdexcept>
12#include <type_traits>
13#include <vector>
14
15namespace pixie::detail {
16
17inline constexpr std::uint8_t kLittleEndianMarker = 1;
18
19template <std::size_t Size>
20void write_magic(BinaryWriter& stream,
21 const std::array<std::uint8_t, Size>& magic) {
22 for (const std::uint8_t byte : magic) {
23 stream.write_u8(byte);
24 }
25}
26
27template <std::size_t Size>
28void require_magic(BinaryReader& reader,
29 const std::array<std::uint8_t, Size>& expected) {
30 for (const std::uint8_t byte : expected) {
31 const std::size_t offset = reader.byte_offset();
32 if (reader.read_u8() != byte) {
33 throw SerializationError("Serialized artifact has the wrong magic",
34 offset);
35 }
36 }
37}
38
39template <std::integral T>
40void write_integral(BinaryWriter& stream, T value) {
41 if constexpr (std::same_as<T, bool>) {
42 stream.write_u8(static_cast<std::uint8_t>(value));
43 } else if constexpr (std::same_as<T, std::size_t>) {
44 stream.write_size(value);
45 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 1) {
46 stream.write_u8(static_cast<std::uint8_t>(value));
47 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 2) {
48 stream.write_u16(static_cast<std::uint16_t>(value));
49 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 4) {
50 stream.write_u32(static_cast<std::uint32_t>(value));
51 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 8) {
52 stream.write_u64(static_cast<std::uint64_t>(value));
53 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 1) {
54 stream.write_i8(static_cast<std::int8_t>(value));
55 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 2) {
56 stream.write_i16(static_cast<std::int16_t>(value));
57 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 4) {
58 stream.write_i32(static_cast<std::int32_t>(value));
59 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 8) {
60 stream.write_i64(static_cast<std::int64_t>(value));
61 } else {
62 static_assert(sizeof(T) == 0, "Unsupported serialized integer type");
63 }
64}
65
66template <std::integral T>
67T read_integral(BinaryReader& reader) {
68 if constexpr (std::same_as<T, bool>) {
69 const std::uint8_t value = reader.read_u8();
70 if (value > 1) {
71 throw SerializationError("Invalid serialized boolean value",
72 reader.byte_offset() - 1);
73 }
74 return value != 0;
75 } else if constexpr (std::same_as<T, std::size_t>) {
76 return reader.read_size();
77 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 1) {
78 return static_cast<T>(reader.read_u8());
79 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 2) {
80 return static_cast<T>(reader.read_u16());
81 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 4) {
82 return static_cast<T>(reader.read_u32());
83 } else if constexpr (std::is_unsigned_v<T> && sizeof(T) == 8) {
84 return static_cast<T>(reader.read_u64());
85 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 1) {
86 return static_cast<T>(reader.read_i8());
87 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 2) {
88 return static_cast<T>(reader.read_i16());
89 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 4) {
90 return static_cast<T>(reader.read_i32());
91 } else if constexpr (std::is_signed_v<T> && sizeof(T) == 8) {
92 return static_cast<T>(reader.read_i64());
93 } else {
94 static_assert(sizeof(T) == 0, "Unsupported serialized integer type");
95 }
96}
97
98template <std::integral T>
99inline constexpr std::size_t kSerializedIntegralBytes =
100 std::same_as<T, std::size_t> ? sizeof(std::uint64_t) : sizeof(T);
101
102template <std::integral T>
103void write_vector(BinaryWriter& stream, std::span<const T> values) {
104 stream.write_size(values.size());
105 for (const T value : values) {
106 write_integral(stream, value);
107 }
108}
109
110template <std::integral T>
111std::vector<T> read_vector(BinaryReader& reader) {
112 const std::size_t count = reader.read_size();
113 const std::vector<T> empty;
114 if (count > empty.max_size()) {
115 throw std::length_error("Serialized vector is too large");
116 }
117 if (count > reader.remaining() / kSerializedIntegralBytes<T>) {
118 throw SerializationError("Truncated serialized vector",
119 reader.byte_offset());
120 }
121 std::vector<T> result;
122 result.reserve(count);
123 for (std::size_t i = 0; i < count; ++i) {
124 result.push_back(read_integral<T>(reader));
125 }
126 return result;
127}
128
129inline std::size_t checked_artifact_size(std::uint64_t encoded_size,
130 std::size_t header_size,
131 std::size_t available_size) {
132 if (encoded_size > std::numeric_limits<std::size_t>::max()) {
133 throw std::length_error("Serialized artifact size does not fit in size_t");
134 }
135 const std::size_t size = static_cast<std::size_t>(encoded_size);
136 if (size < header_size || size > available_size) {
137 throw std::invalid_argument("Truncated serialized artifact");
138 }
139 if (size % sizeof(std::uint64_t) != 0) {
140 throw std::invalid_argument("Serialized artifact is not word padded");
141 }
142 return size;
143}
144
145} // namespace pixie::detail
Checked byte-oriented binary serialization primitives.