Everett
Loading...
Searching...
No Matches
file.h
Go to the documentation of this file.
1
13#pragma once
14
15#include <everett/crc32c.h>
16#include <everett/mapped_file.h>
17#include <everett/object_path.h>
18#include <everett/policy.h>
19
20#include <array>
21#include <cstddef>
22#include <cstdint>
23#include <limits>
24#include <optional>
25#include <span>
26#include <stdexcept>
27#include <string_view>
28#include <utility>
29#include <vector>
30
31namespace everett {
32 enum class file_open_mode : std::uint8_t { checked, trusted };
33
34 template <class P> struct file_header {
35 using policy_type = P;
37 std::uint64_t extent = 0; // Body address units, exactly P::unit.
38 std::uint64_t record_count = 0;
39 std::optional<std::uint64_t> common_value_width;
40 // The writer's value-layout declaration, not the opening registry's
41 // default. Actual record framing is described by common_value_width.
42 std::optional<std::uint64_t> policy_value_width = P::value_width;
43 bool operator==(file_header const &) const = default;
44 };
45
46 namespace file_detail {
47 inline constexpr std::size_t header_bytes = 96;
48 inline constexpr std::uint16_t version = 1;
49
50 // Version 1, explicit little-endian integers; never a raw C++ struct:
51 // 0 magic[8] 8 version:u16 10 header_bytes:u16
52 // 12 flags:u32 16 unit:u8 17 checksum_kind:u8 (=1)
53 // 18 backspace:u8 19 reserved:u8 20 codec_W:u32
54 // 24 group_K:u64
55 // 32 policy_value_width:u64
56 // 40 common_width:u64 48 body_extent:u64
57 // 56 record_count:u64 64 body_crc32c:u32
58 // 68 header_crc32c:u32 72 body_offset:u64 (=96)
59 // 80 total_file_bytes:u64 88 backspace_parameter:u64
60 // Flags: bit0 fixed policy values; bit1 common width present. Header CRC
61 // covers all 96 bytes with bytes68..71 zero. Padding and reserved bits zero.
62 // Backspace: 0 exponential-Golomb (order 0..63), 1 Golomb (modulus >=1).
63 // Byte profiles require descriptor (0,0). Codec width W is independent of
64 // the virtual cascade stride K and is part of the exact stored policy.
65 inline std::uint64_t get(std::span<std::byte const> bytes, std::size_t at, unsigned width) noexcept {
66 std::uint64_t value = 0;
67 for (unsigned i = 0; i < width; ++i) value |= std::uint64_t(std::to_integer<unsigned>(bytes[at + i])) << (i << 3);
68 return value;
69 }
70 inline void put(std::span<std::byte> bytes, std::size_t at, unsigned width, std::uint64_t value) noexcept {
71 for (unsigned i = 0; i < width; ++i) bytes[at + i] = std::byte((value >> (i << 3)) & 255u);
72 }
73 inline std::string_view magic(file_kind kind) {
74 switch (kind) {
75 case file_kind::native_blob: return {"EVRT.KV\0", 8};
76 case file_kind::fractional_index: return {"EVRT.IX\0", 8};
77 }
78 throw std::invalid_argument("unsupported Everett file kind");
79 }
80 template <class P> std::uint64_t body_bytes(std::uint64_t extent) noexcept {
81 if constexpr (P::unit == profile_unit::byte) return extent;
82 else return (extent + 7) >> 3;
83 }
84 template <class P> std::uint64_t total_bytes(std::uint64_t extent) {
85 if constexpr (P::unit == profile_unit::bit)
86 if (extent > std::numeric_limits<std::uint64_t>::max() - 7)
87 throw std::overflow_error("Everett file extent overflow");
88 auto bytes = body_bytes<P>(extent);
89 if (bytes > std::numeric_limits<std::uint64_t>::max() - header_bytes)
90 throw std::overflow_error("Everett file extent overflow");
91 return bytes + header_bytes;
92 }
93 template <class P> void validate_metadata(file_header<P> const & header) {
94 (void)magic(header.kind);
95 if (header.kind == file_kind::fractional_index) {
96 if (header.common_value_width != std::optional<std::uint64_t>{0})
97 throw std::invalid_argument("fractional index values must have width zero");
98 } else if (header.kind == file_kind::native_blob) {
99 if (header.policy_value_width)
100 if (header.common_value_width && header.common_value_width != header.policy_value_width)
101 throw std::invalid_argument("native common width disagrees with fixed policy");
102 auto width = header.common_value_width;
103 if (!width) width = header.policy_value_width;
104 if (width && *width && header.record_count > header.extent / *width)
105 throw std::invalid_argument("fixed value slots exceed body extent");
106 }
107 (void)total_bytes<P>(header.extent);
108 }
109 template <class P> void validate_body(file_header<P> const & header, std::span<std::byte const> body) {
110 if (body_bytes<P>(header.extent) != body.size())
111 throw std::invalid_argument("Everett body extent mismatch");
112 if constexpr (P::unit == profile_unit::bit) {
113 if (header.extent & 7) {
114 unsigned unused = 8 - unsigned(header.extent & 7);
115 if (std::to_integer<unsigned>(body.back()) & ((1u << unused) - 1))
116 throw std::invalid_argument("noncanonical bit-profile tail padding");
117 }
118 }
119 }
120 inline std::uint32_t header_checksum(std::span<std::byte const> bytes) {
121 std::array<std::byte, header_bytes> copy{};
122 for (std::size_t i = 0; i < copy.size(); ++i) copy[i] = bytes[i];
123 put(copy, 68, 4, 0);
124 return crc32c(copy);
125 }
126 }
127
128 template <class P> file_header<P> decode_file_header(std::span<std::byte const> bytes) {
129 if (bytes.size() < file_detail::header_bytes) throw std::invalid_argument("truncated Everett header");
130 file_header<P> header;
131 bool recognized = false;
132 for (auto kind : {file_kind::native_blob, file_kind::fractional_index}) {
133 auto magic = file_detail::magic(kind);
134 bool match = true;
135 for (std::size_t i = 0; i < 8; ++i)
136 if (std::to_integer<unsigned char>(bytes[i]) != static_cast<unsigned char>(magic[i])) match = false;
137 if (match) { header.kind = kind; recognized = true; break; }
138 }
139 if (!recognized) throw std::invalid_argument("unrecognized Everett magic");
140 if (file_detail::get(bytes, 8, 2) != file_detail::version ||
141 file_detail::get(bytes, 10, 2) != file_detail::header_bytes)
142 throw std::invalid_argument("unsupported Everett file version or header length");
143 if (file_detail::get(bytes, 68, 4) != file_detail::header_checksum(bytes))
144 throw std::invalid_argument("Everett header CRC32C mismatch");
145 auto flags = file_detail::get(bytes, 12, 4);
146 if (flags & ~std::uint64_t{3}) throw std::invalid_argument("unsupported Everett header flags");
147 if (file_detail::get(bytes, 16, 1) > 1 ||
148 file_detail::get(bytes, 16, 1) != static_cast<unsigned>(P::unit))
149 throw std::invalid_argument("Everett address unit disagrees with policy");
150 if (file_detail::get(bytes, 17, 1) != 1) throw std::invalid_argument("unsupported Everett checksum kind");
151 if (file_detail::get(bytes, 19, 1))
152 throw std::invalid_argument("nonzero Everett reserved header bytes");
153 auto backspace = file_detail::get(bytes, 18, 1);
154 auto backspace_parameter = file_detail::get(bytes, 88, 8);
155 if (backspace > static_cast<unsigned>(bit_backspace_code::golomb) ||
156 (backspace == static_cast<unsigned>(bit_backspace_code::exponential_golomb) && backspace_parameter > 63) ||
157 (backspace == static_cast<unsigned>(bit_backspace_code::golomb) && !backspace_parameter) ||
158 (P::unit == profile_unit::byte && (backspace || backspace_parameter)))
159 throw std::invalid_argument("unsupported Everett backspace descriptor");
160 if (file_detail::get(bytes, 20, 4) != P::codec_block_size ||
161 file_detail::get(bytes, 24, 8) != P::group_size ||
162 backspace != static_cast<unsigned>(P::backspace_code) || backspace_parameter != P::backspace_parameter)
163 throw std::invalid_argument("Everett stored policy descriptor mismatch");
164 header.policy_value_width.reset();
165 if (flags & 1) header.policy_value_width = file_detail::get(bytes, 32, 8);
166 else if (file_detail::get(bytes, 32, 8))
167 throw std::invalid_argument("variable policy width must encode zero");
168 if (flags & 2) header.common_value_width = file_detail::get(bytes, 40, 8);
169 else if (file_detail::get(bytes, 40, 8)) throw std::invalid_argument("absent common width must encode zero");
170 header.extent = file_detail::get(bytes, 48, 8);
171 header.record_count = file_detail::get(bytes, 56, 8);
172 file_detail::validate_metadata(header);
173 if (file_detail::get(bytes, 72, 8) != file_detail::header_bytes ||
174 file_detail::get(bytes, 80, 8) != file_detail::total_bytes<P>(header.extent))
175 throw std::invalid_argument("noncanonical Everett body offset or physical extent");
176 return header;
177 }
178
179 template <class P> file_header<P> validate_file(std::span<std::byte const> bytes) {
180 auto header = decode_file_header<P>(bytes);
181 if (file_detail::total_bytes<P>(header.extent) != bytes.size())
182 throw std::invalid_argument("truncated or trailing Everett object bytes");
183 auto body = bytes.subspan(file_detail::header_bytes);
184 file_detail::validate_body(header, body);
185 if (file_detail::get(bytes, 64, 4) != crc32c<typename P::architecture>(body))
186 throw std::invalid_argument("Everett body CRC32C mismatch");
187 return header;
188 }
189
190 // Serialize only the canonical envelope. The caller supplies the finalized
191 // CRC of the physical body bytes and remains responsible for checking the
192 // body's extent and bit padding before publication. No body is read here.
193 template <class P> std::array<std::byte, file_detail::header_bytes>
194 encode_file_header(file_header<P> const & header, std::uint32_t body_crc) {
195 file_detail::validate_metadata(header);
196 auto total = file_detail::total_bytes<P>(header.extent);
197 std::array<std::byte, file_detail::header_bytes> result{};
198 auto magic = file_detail::magic(header.kind);
199 for (std::size_t i = 0; i < 8; ++i) result[i] = std::byte(static_cast<unsigned char>(magic[i]));
200 file_detail::put(result, 8, 2, file_detail::version);
201 file_detail::put(result, 10, 2, file_detail::header_bytes);
202 file_detail::put(result, 12, 4, (header.policy_value_width ? 1u : 0u) | (header.common_value_width ? 2u : 0u));
203 file_detail::put(result, 16, 1, static_cast<unsigned>(P::unit));
204 file_detail::put(result, 17, 1, 1);
205 file_detail::put(result, 18, 1, static_cast<unsigned>(P::backspace_code));
206 file_detail::put(result, 20, 4, P::codec_block_size);
207 file_detail::put(result, 24, 8, P::group_size);
208 file_detail::put(result, 32, 8, header.policy_value_width.value_or(0));
209 file_detail::put(result, 40, 8, header.common_value_width.value_or(0));
210 file_detail::put(result, 48, 8, header.extent);
211 file_detail::put(result, 56, 8, header.record_count);
212 file_detail::put(result, 64, 4, body_crc);
213 file_detail::put(result, 72, 8, file_detail::header_bytes);
214 file_detail::put(result, 80, 8, total);
215 file_detail::put(result, 88, 8, P::backspace_parameter);
216 file_detail::put(result, 68, 4, file_detail::header_checksum(result));
217 return result;
218 }
219
220 // Pure serialization of an already encoded body; no file writes or durability.
221 template <class P> std::vector<std::byte> encode_file(file_header<P> const & header,
222 std::span<std::byte const> body) {
223 file_detail::validate_metadata(header);
224 file_detail::validate_body(header, body);
225 auto total = file_detail::total_bytes<P>(header.extent);
226 if (total > std::numeric_limits<std::size_t>::max()) throw std::length_error("Everett file is too large");
227 auto prefix = encode_file_header(header, crc32c<typename P::architecture>(body));
228 std::vector<std::byte> result(static_cast<std::size_t>(total));
229 for (std::size_t i = 0; i < prefix.size(); ++i) result[i] = prefix[i];
230 for (std::size_t i = 0; i < body.size(); ++i) result[file_detail::header_bytes + i] = body[i];
231 return result;
232 }
233
234 // Typed single-object reader. Checked opening validates the header and exact
235 // file extent without reading payload bytes. Trusted opening reads no mapped
236 // bytes: the caller vouches for the immutable object's format and policy.
237 // Both modes retain a mapping and require at least the fixed 96-byte header.
238 // body() takes the physical remainder without decoding metadata. header()
239 // returns a value, checking trusted metadata on explicit access without a
240 // mutable lazy cache. scan() always validates the entire object, including
241 // its header: O(physical body bytes). It does not validate the external name.
242 template <class P> struct file {
243 using policy_type = P;
244 static file from_slice(mapped_slice bytes, file_open_mode mode = file_open_mode::checked) {
245 switch (mode) {
246 case file_open_mode::checked: {
247 auto header = checked_header(bytes);
248 return {std::move(bytes), std::move(header)};
249 }
250 case file_open_mode::trusted:
251 if (bytes.size() < file_detail::header_bytes)
252 throw std::invalid_argument("truncated Everett header");
253 return {std::move(bytes), std::nullopt};
254 }
255 throw std::invalid_argument("unsupported Everett file open mode");
256 }
257 static file open(std::filesystem::path const & path, file_open_mode mode = file_open_mode::checked) {
258 auto mapping = mapped_file::open(path);
259 // All mapped-byte access is in the same helper exercised by from_slice.
260 auto result = from_slice(mapping.slice(0, mapping.size()), mode);
261 // Trusted callers also vouch for the filename; checking its agreement
262 // would require decoding the header that this mode deliberately skips.
263 if (mode == file_open_mode::checked &&
264 path.extension().generic_string() != file_extension(result.header_->kind))
265 throw std::invalid_argument("Everett extension disagrees with file magic");
266 return result;
267 }
268 file_header<P> header() const { return header_ ? *header_ : checked_header(bytes_); }
269 mapped_slice body() const { return bytes_.slice(file_detail::header_bytes, bytes_.size() - file_detail::header_bytes); }
270 void scan() const { (void)validate_file<P>(bytes_.bytes()); }
271 private:
273 auto header = decode_file_header<P>(bytes.bytes());
274 if (file_detail::total_bytes<P>(header.extent) != bytes.size())
275 throw std::invalid_argument("truncated or trailing Everett object bytes");
276 return header;
277 }
278 file(mapped_slice bytes, std::optional<file_header<P>> header) : bytes_(std::move(bytes)), header_(std::move(header)) {}
280 std::optional<file_header<P>> header_;
281 };
282}
Declares Everett's CRC32C support.
Declares Everett's mapped file support.
std::uint32_t header_checksum(std::span< std::byte const > bytes)
Definition file.h:120
void put(std::span< std::byte > bytes, std::size_t at, unsigned width, std::uint64_t value) noexcept
Definition file.h:70
void validate_body(file_header< P > const &header, std::span< std::byte const > body)
Definition file.h:109
constexpr std::uint16_t version
Definition file.h:48
void validate_metadata(file_header< P > const &header)
Definition file.h:93
std::uint64_t body_bytes(std::uint64_t extent) noexcept
Definition file.h:80
std::uint64_t total_bytes(std::uint64_t extent)
Definition file.h:84
std::uint64_t get(std::span< std::byte const > bytes, std::size_t at, unsigned width) noexcept
Definition file.h:65
std::string_view magic(file_kind kind)
Definition file.h:73
constexpr std::size_t header_bytes
Definition file.h:47
Definition active_engine.h:18
std::uint32_t crc32c(std::span< std::byte const > bytes, std::uint32_t previous_crc=0) noexcept
file_header< P > validate_file(std::span< std::byte const > bytes)
Definition file.h:179
file_header< P > decode_file_header(std::span< std::byte const > bytes)
Definition file.h:128
std::array< std::byte, file_detail::header_bytes > encode_file_header(file_header< P > const &header, std::uint32_t body_crc)
Definition file.h:194
file_open_mode
Definition file.h:32
std::vector< std::byte > encode_file(file_header< P > const &header, std::span< std::byte const > body)
Definition file.h:221
std::string_view file_extension(file_kind kind)
Definition object_path.h:27
file_kind
Definition object_path.h:25
Declares Everett's object path support.
Declares Everett's policy support.
Definition file.h:34
std::uint64_t record_count
Definition file.h:38
std::optional< std::uint64_t > common_value_width
Definition file.h:39
P policy_type
Definition file.h:35
bool operator==(file_header const &) const =default
file_kind kind
Definition file.h:36
std::uint64_t extent
Definition file.h:37
std::optional< std::uint64_t > policy_value_width
Definition file.h:42
Definition file.h:242
file_header< P > header() const
Definition file.h:268
std::optional< file_header< P > > header_
Definition file.h:280
mapped_slice body() const
Definition file.h:269
static file from_slice(mapped_slice bytes, file_open_mode mode=file_open_mode::checked)
Definition file.h:244
void scan() const
Definition file.h:270
mapped_slice bytes_
Definition file.h:279
P policy_type
Definition file.h:243
static file open(std::filesystem::path const &path, file_open_mode mode=file_open_mode::checked)
Definition file.h:257
file(mapped_slice bytes, std::optional< file_header< P > > header)
Definition file.h:278
static file_header< P > checked_header(mapped_slice const &bytes)
Definition file.h:272
Definition mapped_file.h:81
mapped_slice slice(std::uint64_t offset, std::uint64_t length) const
Definition mapped_file.h:106