1#![deny(missing_docs)]
25
26pub mod catalog;
27pub mod document;
28pub mod page;
29pub mod record;
30pub mod superblock;
31pub mod vector;
32
33pub use catalog::{Band, CatalogEntry, Model, ValueType};
34pub use document::{
35 DOC_COUNT_MAX, DOC_COUNT_SHIFT, DOC_HEADER_LEN, DocumentBody, ValueTag, doc_flags,
36};
37pub use page::{PAGE_HEADER_LEN, PageHeader};
38pub use record::{RecordHeader, RecordKind, RecordRef, record_flags};
39pub use superblock::{CheckpointEntry, Superblock, superblock_flags};
40pub use vector::{Element, VECTOR_HEADER_LEN, VectorBody};
41
42pub const MAGIC: [u8; 16] = *b"tamndyo fmt001\0\0";
48
49pub const FORMAT_VERSION: u32 = 1;
51
52pub const MIN_READER_VERSION: u32 = 1;
59
60const _: () = assert!(MIN_READER_VERSION <= FORMAT_VERSION);
64
65pub const SUPERBLOCK_LEN: usize = 16 * 1024;
67
68pub const DATA_START: u64 = 2 * SUPERBLOCK_LEN as u64;
70
71pub const DEFAULT_PAGE_SIZE: u32 = 16384;
73
74pub const MIN_PAGE_SIZE: u32 = 4096;
79
80pub const MAX_PAGE_SIZE: u32 = 65536;
82
83pub const LOG_PAGE_LEN: u64 = 32 * 1024 * 1024;
89
90pub const RECORD_ALIGN: usize = 8;
92
93#[must_use]
99pub const fn is_legal_page_size(n: u32) -> bool {
100 n.is_power_of_two() && n >= MIN_PAGE_SIZE && n <= MAX_PAGE_SIZE
101}
102
103#[inline]
105#[must_use]
106pub const fn align_up(n: usize) -> usize {
107 n.next_multiple_of(RECORD_ALIGN)
108}
109
110#[inline]
124#[must_use]
125pub fn get_u8(b: &[u8], off: usize) -> u8 {
126 b.get(off).copied().unwrap_or(0)
127}
128
129#[inline]
131#[must_use]
132pub fn get_u16(b: &[u8], off: usize) -> u16 {
133 match b.get(off..off + 2) {
134 Some(s) => u16::from_le_bytes([s[0], s[1]]),
135 None => 0,
136 }
137}
138
139#[inline]
141#[must_use]
142pub fn get_u32(b: &[u8], off: usize) -> u32 {
143 match b.get(off..off + 4) {
144 Some(s) => u32::from_le_bytes([s[0], s[1], s[2], s[3]]),
145 None => 0,
146 }
147}
148
149#[inline]
151#[must_use]
152pub fn get_u64(b: &[u8], off: usize) -> u64 {
153 match b.get(off..off + 8) {
154 Some(s) => u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]),
155 None => 0,
156 }
157}
158
159#[inline]
161pub fn put_u8(b: &mut [u8], off: usize, v: u8) {
162 if let Some(slot) = b.get_mut(off) {
163 *slot = v;
164 }
165}
166
167#[inline]
169pub fn put_u16(b: &mut [u8], off: usize, v: u16) {
170 if let Some(s) = b.get_mut(off..off + 2) {
171 s.copy_from_slice(&v.to_le_bytes());
172 }
173}
174
175#[inline]
177pub fn put_u32(b: &mut [u8], off: usize, v: u32) {
178 if let Some(s) = b.get_mut(off..off + 4) {
179 s.copy_from_slice(&v.to_le_bytes());
180 }
181}
182
183#[inline]
185pub fn put_u64(b: &mut [u8], off: usize, v: u64) {
186 if let Some(s) = b.get_mut(off..off + 8) {
187 s.copy_from_slice(&v.to_le_bytes());
188 }
189}
190
191#[must_use]
198pub fn checksum_skipping(bytes: &[u8], skip: usize) -> u32 {
199 if skip + 4 > bytes.len() {
200 return yo_common::crc32c(0, bytes);
201 }
202 let c = yo_common::crc32c(0, &bytes[..skip]);
203 let c = yo_common::crc32c(c, &[0, 0, 0, 0]);
204 yo_common::crc32c(c, &bytes[skip + 4..])
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn the_magic_is_what_the_specification_says() {
213 assert_eq!(MAGIC.len(), 16);
214 assert_eq!(&MAGIC[..14], b"tamndyo fmt001");
215 assert_eq!(&MAGIC[14..], b"\0\0");
216 }
217
218 #[test]
219 fn page_sizes_are_powers_of_two_in_range() {
220 assert!(is_legal_page_size(4096));
221 assert!(is_legal_page_size(16384));
222 assert!(is_legal_page_size(65536));
223 assert!(!is_legal_page_size(2048), "below the torn write unit");
224 assert!(!is_legal_page_size(131_072), "above the maximum");
225 assert!(!is_legal_page_size(12288), "not a power of two");
226 assert!(!is_legal_page_size(0));
227 assert!(is_legal_page_size(DEFAULT_PAGE_SIZE));
228 }
229
230 #[test]
231 fn a_log_page_is_a_whole_number_of_segments_at_every_legal_size() {
232 let mut n = MIN_PAGE_SIZE;
233 while n <= MAX_PAGE_SIZE {
234 assert_eq!(
235 LOG_PAGE_LEN % u64::from(n),
236 0,
237 "a 32 MiB log page must divide into {n} byte segments"
238 );
239 n *= 2;
240 }
241 assert_eq!(LOG_PAGE_LEN / u64::from(DEFAULT_PAGE_SIZE), 2048);
242 }
243
244 #[test]
245 fn alignment_rounds_up_and_leaves_aligned_values_alone() {
246 assert_eq!(align_up(0), 0);
247 assert_eq!(align_up(1), 8);
248 assert_eq!(align_up(8), 8);
249 assert_eq!(align_up(9), 16);
250 assert_eq!(align_up(RECORD_ALIGN * 3), RECORD_ALIGN * 3);
251 }
252
253 #[test]
254 fn data_starts_after_both_superblock_slots() {
255 assert_eq!(DATA_START, 32768);
256 assert_eq!(SUPERBLOCK_LEN, 16384);
257 }
258
259 #[test]
260 fn short_reads_give_zero_rather_than_panicking() {
261 let b = [1u8, 2, 3];
262 assert_eq!(get_u8(&b, 0), 1);
263 assert_eq!(get_u8(&b, 9), 0);
264 assert_eq!(get_u16(&b, 0), 0x0201);
265 assert_eq!(
266 get_u16(&b, 2),
267 0,
268 "would need two bytes and only one is left"
269 );
270 assert_eq!(get_u32(&b, 0), 0);
271 assert_eq!(get_u64(&b, 0), 0);
272 }
273
274 #[test]
275 fn short_writes_do_nothing_rather_than_panicking() {
276 let mut b = [0u8; 3];
277 put_u32(&mut b, 0, 0xdead_beef);
278 assert_eq!(b, [0, 0, 0], "no room, so nothing was written");
279 put_u16(&mut b, 0, 0x1234);
280 assert_eq!(b, [0x34, 0x12, 0]);
281 }
282
283 #[test]
284 fn round_trips_are_little_endian_on_every_machine() {
285 let mut b = [0u8; 8];
286 put_u64(&mut b, 0, 0x0102_0304_0506_0708);
287 assert_eq!(b, [8, 7, 6, 5, 4, 3, 2, 1], "little endian, byte for byte");
288 assert_eq!(get_u64(&b, 0), 0x0102_0304_0506_0708);
289 }
290
291 #[test]
292 fn the_checksum_reads_its_own_field_as_zero() {
293 let mut b = vec![0u8; 32];
294 for (i, slot) in b.iter_mut().enumerate() {
295 *slot = i as u8;
296 }
297 let want = checksum_skipping(&b, 28);
298 put_u32(&mut b, 28, want);
301 assert_eq!(checksum_skipping(&b, 28), want);
302 put_u32(&mut b, 28, 0xffff_ffff);
303 assert_eq!(checksum_skipping(&b, 28), want);
304 b[3] ^= 1;
306 assert_ne!(checksum_skipping(&b, 28), want);
307 }
308}