1#![deny(missing_docs)]
25
26pub mod catalog;
27pub mod document;
28pub mod image;
29pub mod page;
30pub mod record;
31pub mod superblock;
32pub mod vector;
33
34pub use catalog::{Band, CatalogEntry, Model, ValueType};
35pub use document::{
36 DOC_COUNT_MAX, DOC_COUNT_SHIFT, DOC_HEADER_LEN, DocumentBody, ValueTag, doc_flags,
37};
38pub use image::{
39 Chain, IMAGE_HEADER_LEN, ImageHeader, POSTING_HEADER_LEN, PostingHeader, image_kind,
40};
41pub use page::{PAGE_HEADER_LEN, PageHeader};
42pub use record::{RecordHeader, RecordKind, RecordRef, record_flags};
43pub use superblock::{CheckpointEntry, Superblock, superblock_flags};
44pub use vector::{Element, VECTOR_HEADER_LEN, VectorBody};
45
46pub const MAGIC: [u8; 16] = *b"tamndyo fmt001\0\0";
52
53pub const FORMAT_VERSION: u32 = 1;
55
56pub const MIN_READER_VERSION: u32 = 1;
63
64const _: () = assert!(MIN_READER_VERSION <= FORMAT_VERSION);
68
69pub const SUPERBLOCK_LEN: usize = 16 * 1024;
71
72pub const DATA_START: u64 = 2 * SUPERBLOCK_LEN as u64;
74
75pub const DEFAULT_PAGE_SIZE: u32 = 16384;
77
78pub const MIN_PAGE_SIZE: u32 = 4096;
83
84pub const MAX_PAGE_SIZE: u32 = 65536;
86
87pub const LOG_PAGE_LEN: u64 = 32 * 1024 * 1024;
93
94pub const RECORD_ALIGN: usize = 8;
96
97#[must_use]
103pub const fn is_legal_page_size(n: u32) -> bool {
104 n.is_power_of_two() && n >= MIN_PAGE_SIZE && n <= MAX_PAGE_SIZE
105}
106
107#[inline]
109#[must_use]
110pub const fn align_up(n: usize) -> usize {
111 n.next_multiple_of(RECORD_ALIGN)
112}
113
114#[inline]
128#[must_use]
129pub fn get_u8(b: &[u8], off: usize) -> u8 {
130 b.get(off).copied().unwrap_or(0)
131}
132
133#[inline]
135#[must_use]
136pub fn get_u16(b: &[u8], off: usize) -> u16 {
137 match b.get(off..off + 2) {
138 Some(s) => u16::from_le_bytes([s[0], s[1]]),
139 None => 0,
140 }
141}
142
143#[inline]
145#[must_use]
146pub fn get_u32(b: &[u8], off: usize) -> u32 {
147 match b.get(off..off + 4) {
148 Some(s) => u32::from_le_bytes([s[0], s[1], s[2], s[3]]),
149 None => 0,
150 }
151}
152
153#[inline]
155#[must_use]
156pub fn get_u64(b: &[u8], off: usize) -> u64 {
157 match b.get(off..off + 8) {
158 Some(s) => u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]),
159 None => 0,
160 }
161}
162
163#[inline]
169#[must_use]
170pub fn get_f32(b: &[u8], off: usize) -> f32 {
171 match b.get(off..off + 4) {
172 Some(s) => f32::from_le_bytes([s[0], s[1], s[2], s[3]]),
173 None => 0.0,
174 }
175}
176
177#[inline]
179pub fn put_f32(b: &mut [u8], off: usize, v: f32) {
180 if let Some(s) = b.get_mut(off..off + 4) {
181 s.copy_from_slice(&v.to_le_bytes());
182 }
183}
184
185#[inline]
187pub fn put_u8(b: &mut [u8], off: usize, v: u8) {
188 if let Some(slot) = b.get_mut(off) {
189 *slot = v;
190 }
191}
192
193#[inline]
195pub fn put_u16(b: &mut [u8], off: usize, v: u16) {
196 if let Some(s) = b.get_mut(off..off + 2) {
197 s.copy_from_slice(&v.to_le_bytes());
198 }
199}
200
201#[inline]
203pub fn put_u32(b: &mut [u8], off: usize, v: u32) {
204 if let Some(s) = b.get_mut(off..off + 4) {
205 s.copy_from_slice(&v.to_le_bytes());
206 }
207}
208
209#[inline]
211pub fn put_u64(b: &mut [u8], off: usize, v: u64) {
212 if let Some(s) = b.get_mut(off..off + 8) {
213 s.copy_from_slice(&v.to_le_bytes());
214 }
215}
216
217#[must_use]
224pub fn checksum_skipping(bytes: &[u8], skip: usize) -> u32 {
225 if skip + 4 > bytes.len() {
226 return yo_common::crc32c(0, bytes);
227 }
228 let c = yo_common::crc32c(0, &bytes[..skip]);
229 let c = yo_common::crc32c(c, &[0, 0, 0, 0]);
230 yo_common::crc32c(c, &bytes[skip + 4..])
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn the_magic_is_what_the_specification_says() {
239 assert_eq!(MAGIC.len(), 16);
240 assert_eq!(&MAGIC[..14], b"tamndyo fmt001");
241 assert_eq!(&MAGIC[14..], b"\0\0");
242 }
243
244 #[test]
245 fn page_sizes_are_powers_of_two_in_range() {
246 assert!(is_legal_page_size(4096));
247 assert!(is_legal_page_size(16384));
248 assert!(is_legal_page_size(65536));
249 assert!(!is_legal_page_size(2048), "below the torn write unit");
250 assert!(!is_legal_page_size(131_072), "above the maximum");
251 assert!(!is_legal_page_size(12288), "not a power of two");
252 assert!(!is_legal_page_size(0));
253 assert!(is_legal_page_size(DEFAULT_PAGE_SIZE));
254 }
255
256 #[test]
257 fn a_log_page_is_a_whole_number_of_segments_at_every_legal_size() {
258 let mut n = MIN_PAGE_SIZE;
259 while n <= MAX_PAGE_SIZE {
260 assert_eq!(
261 LOG_PAGE_LEN % u64::from(n),
262 0,
263 "a 32 MiB log page must divide into {n} byte segments"
264 );
265 n *= 2;
266 }
267 assert_eq!(LOG_PAGE_LEN / u64::from(DEFAULT_PAGE_SIZE), 2048);
268 }
269
270 #[test]
271 fn alignment_rounds_up_and_leaves_aligned_values_alone() {
272 assert_eq!(align_up(0), 0);
273 assert_eq!(align_up(1), 8);
274 assert_eq!(align_up(8), 8);
275 assert_eq!(align_up(9), 16);
276 assert_eq!(align_up(RECORD_ALIGN * 3), RECORD_ALIGN * 3);
277 }
278
279 #[test]
280 fn data_starts_after_both_superblock_slots() {
281 assert_eq!(DATA_START, 32768);
282 assert_eq!(SUPERBLOCK_LEN, 16384);
283 }
284
285 #[test]
286 fn short_reads_give_zero_rather_than_panicking() {
287 let b = [1u8, 2, 3];
288 assert_eq!(get_u8(&b, 0), 1);
289 assert_eq!(get_u8(&b, 9), 0);
290 assert_eq!(get_u16(&b, 0), 0x0201);
291 assert_eq!(
292 get_u16(&b, 2),
293 0,
294 "would need two bytes and only one is left"
295 );
296 assert_eq!(get_u32(&b, 0), 0);
297 assert_eq!(get_u64(&b, 0), 0);
298 }
299
300 #[test]
301 fn short_writes_do_nothing_rather_than_panicking() {
302 let mut b = [0u8; 3];
303 put_u32(&mut b, 0, 0xdead_beef);
304 assert_eq!(b, [0, 0, 0], "no room, so nothing was written");
305 put_u16(&mut b, 0, 0x1234);
306 assert_eq!(b, [0x34, 0x12, 0]);
307 }
308
309 #[test]
310 fn a_float_goes_down_and_comes_back_bit_for_bit() {
311 let mut b = [0u8; 4];
312 for v in [0.0f32, -0.0, 1.0, -1.0, 1e-38, 3.4e38, 0.1] {
313 put_f32(&mut b, 0, v);
314 assert_eq!(
315 get_f32(&b, 0).to_bits(),
316 v.to_bits(),
317 "{v} came back changed"
318 );
319 }
320 assert_eq!(get_f32(&b, 2), 0.0, "two bytes are not a float");
321 }
322
323 #[test]
324 fn round_trips_are_little_endian_on_every_machine() {
325 let mut b = [0u8; 8];
326 put_u64(&mut b, 0, 0x0102_0304_0506_0708);
327 assert_eq!(b, [8, 7, 6, 5, 4, 3, 2, 1], "little endian, byte for byte");
328 assert_eq!(get_u64(&b, 0), 0x0102_0304_0506_0708);
329 }
330
331 #[test]
332 fn the_checksum_reads_its_own_field_as_zero() {
333 let mut b = vec![0u8; 32];
334 for (i, slot) in b.iter_mut().enumerate() {
335 *slot = i as u8;
336 }
337 let want = checksum_skipping(&b, 28);
338 put_u32(&mut b, 28, want);
341 assert_eq!(checksum_skipping(&b, 28), want);
342 put_u32(&mut b, 28, 0xffff_ffff);
343 assert_eq!(checksum_skipping(&b, 28), want);
344 b[3] ^= 1;
346 assert_ne!(checksum_skipping(&b, 28), want);
347 }
348}