1use std::fmt;
7
8pub const PAGE_FILE_MAGIC: [u8; 4] = [0x52, 0x44, 0x44, 0x42];
10
11pub const PAGE_FILE_VERSION: u32 = 0x0001_0000;
13
14pub const PAGED_PAGE_SIZE: usize = 16_384;
16
17pub const PAGED_PAGE_HEADER_SIZE: usize = 32;
19
20pub const PAGED_CELL_POINTER_SIZE: usize = 2;
21pub const PAGED_CELL_HEADER_SIZE: usize = 6;
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub struct PagedPageHeader {
26 pub page_type: u8,
27 pub flags: u8,
28 pub cell_count: u16,
29 pub free_start: u16,
30 pub free_end: u16,
31 pub page_id: u32,
32 pub parent_id: u32,
33 pub right_child: u32,
34 pub lsn: u64,
35 pub checksum: u32,
36}
37
38pub fn encode_paged_page_header(header: &PagedPageHeader) -> [u8; PAGED_PAGE_HEADER_SIZE] {
39 let mut buf = [0u8; PAGED_PAGE_HEADER_SIZE];
40 buf[0] = header.page_type;
41 buf[1] = header.flags;
42 buf[2..4].copy_from_slice(&header.cell_count.to_le_bytes());
43 buf[4..6].copy_from_slice(&header.free_start.to_le_bytes());
44 buf[6..8].copy_from_slice(&header.free_end.to_le_bytes());
45 buf[8..12].copy_from_slice(&header.page_id.to_le_bytes());
46 buf[12..16].copy_from_slice(&header.parent_id.to_le_bytes());
47 buf[16..20].copy_from_slice(&header.right_child.to_le_bytes());
48 buf[20..28].copy_from_slice(&header.lsn.to_le_bytes());
49 buf[28..32].copy_from_slice(&header.checksum.to_le_bytes());
50 buf
51}
52
53pub fn decode_paged_page_header(buf: &[u8; PAGED_PAGE_HEADER_SIZE]) -> PagedPageHeader {
54 PagedPageHeader {
55 page_type: buf[0],
56 flags: buf[1],
57 cell_count: u16::from_le_bytes([buf[2], buf[3]]),
58 free_start: u16::from_le_bytes([buf[4], buf[5]]),
59 free_end: u16::from_le_bytes([buf[6], buf[7]]),
60 page_id: u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
61 parent_id: u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]),
62 right_child: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]),
63 lsn: u64::from_le_bytes([
64 buf[20], buf[21], buf[22], buf[23], buf[24], buf[25], buf[26], buf[27],
65 ]),
66 checksum: u32::from_le_bytes([buf[28], buf[29], buf[30], buf[31]]),
67 }
68}
69
70pub fn paged_page_type(page: &[u8; PAGED_PAGE_SIZE]) -> u8 {
71 page[0]
72}
73
74pub fn paged_page_id(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
75 read_u32(page, 8).expect("paged page has header")
76}
77
78pub fn paged_page_lsn(page: &[u8; PAGED_PAGE_SIZE]) -> u64 {
79 read_u64(page, 20).expect("paged page has header")
80}
81
82pub fn set_paged_page_lsn(page: &mut [u8; PAGED_PAGE_SIZE], lsn: u64) {
83 write_u64(page, 20, lsn).expect("paged page has header");
84}
85
86pub fn paged_page_cell_count(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
87 read_u16(page, 2).expect("paged page has header")
88}
89
90pub fn set_paged_page_cell_count(page: &mut [u8; PAGED_PAGE_SIZE], count: u16) {
91 write_u16(page, 2, count).expect("paged page has header");
92}
93
94pub fn paged_page_parent_id(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
95 read_u32(page, 12).expect("paged page has header")
96}
97
98pub fn set_paged_page_parent_id(page: &mut [u8; PAGED_PAGE_SIZE], parent_id: u32) {
99 write_u32(page, 12, parent_id).expect("paged page has header");
100}
101
102pub fn paged_page_right_child(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
103 read_u32(page, 16).expect("paged page has header")
104}
105
106pub fn set_paged_page_right_child(page: &mut [u8; PAGED_PAGE_SIZE], child_id: u32) {
107 write_u32(page, 16, child_id).expect("paged page has header");
108}
109
110pub fn paged_page_free_start(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
111 read_u16(page, 4).expect("paged page has header")
112}
113
114pub fn set_paged_page_free_start(page: &mut [u8; PAGED_PAGE_SIZE], offset: u16) {
115 write_u16(page, 4, offset).expect("paged page has header");
116}
117
118pub fn paged_page_free_end(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
119 read_u16(page, 6).expect("paged page has header")
120}
121
122pub fn set_paged_page_free_end(page: &mut [u8; PAGED_PAGE_SIZE], offset: u16) {
123 write_u16(page, 6, offset).expect("paged page has header");
124}
125
126pub fn paged_page_checksum(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
127 read_u32(page, 28).expect("paged page has header")
128}
129
130pub fn clear_paged_page_checksum(page: &mut [u8; PAGED_PAGE_SIZE]) {
131 write_u32(page, 28, 0).expect("paged page has header");
132}
133
134pub fn set_paged_page_checksum(page: &mut [u8; PAGED_PAGE_SIZE], checksum: u32) {
135 write_u32(page, 28, checksum).expect("paged page has header");
136}
137
138pub fn paged_cell_pointer_offset(index: usize) -> Option<usize> {
139 let offset = PAGED_PAGE_HEADER_SIZE.checked_add(index.checked_mul(PAGED_CELL_POINTER_SIZE)?)?;
140 (offset + PAGED_CELL_POINTER_SIZE <= PAGED_PAGE_SIZE).then_some(offset)
141}
142
143pub fn paged_cell_pointer(page: &[u8; PAGED_PAGE_SIZE], index: usize) -> Option<u16> {
144 read_u16(page, paged_cell_pointer_offset(index)?).ok()
145}
146
147pub fn set_paged_cell_pointer(
148 page: &mut [u8; PAGED_PAGE_SIZE],
149 index: usize,
150 pointer: u16,
151) -> bool {
152 let Some(offset) = paged_cell_pointer_offset(index) else {
153 return false;
154 };
155 if !paged_cell_pointer_is_valid(pointer) {
156 return false;
157 }
158 write_u16(page, offset, pointer).is_ok()
159}
160
161pub fn paged_cell_pointer_is_valid(pointer: u16) -> bool {
162 let pointer = pointer as usize;
163 (PAGED_PAGE_HEADER_SIZE..PAGED_PAGE_SIZE).contains(&pointer)
164}
165
166pub fn paged_cell_len(key_len: usize, value_len: usize) -> Option<usize> {
167 PAGED_CELL_HEADER_SIZE
168 .checked_add(key_len)?
169 .checked_add(value_len)
170}
171
172pub fn paged_cell_total_len(page: &[u8; PAGED_PAGE_SIZE], pointer: u16) -> Option<usize> {
173 let pointer = pointer as usize;
174 if pointer + PAGED_CELL_HEADER_SIZE > PAGED_PAGE_SIZE {
175 return None;
176 }
177 let key_len = read_u16(page, pointer).ok()? as usize;
178 let value_len = read_u32(page, pointer + 2).ok()? as usize;
179 let total_len = paged_cell_len(key_len, value_len)?;
180 (pointer + total_len <= PAGED_PAGE_SIZE).then_some(total_len)
181}
182
183pub fn paged_cell_bytes(page: &[u8; PAGED_PAGE_SIZE], pointer: u16) -> Option<&[u8]> {
184 let total_len = paged_cell_total_len(page, pointer)?;
185 let pointer = pointer as usize;
186 Some(&page[pointer..pointer + total_len])
187}
188
189pub fn paged_cell_key_value(cell: &[u8]) -> Option<(&[u8], &[u8])> {
190 if cell.len() < PAGED_CELL_HEADER_SIZE {
191 return None;
192 }
193 let key_len = u16::from_le_bytes(cell[0..2].try_into().ok()?) as usize;
194 let value_len = u32::from_le_bytes(cell[2..6].try_into().ok()?) as usize;
195 let total_len = paged_cell_len(key_len, value_len)?;
196 if total_len > cell.len() {
197 return None;
198 }
199 let key_start = PAGED_CELL_HEADER_SIZE;
200 let value_start = key_start + key_len;
201 Some((
202 &cell[key_start..value_start],
203 &cell[value_start..value_start + value_len],
204 ))
205}
206
207pub fn write_paged_cell(
208 page: &mut [u8; PAGED_PAGE_SIZE],
209 offset: u16,
210 key: &[u8],
211 value: &[u8],
212) -> bool {
213 let Ok(key_len) = u16::try_from(key.len()) else {
214 return false;
215 };
216 let Ok(value_len) = u32::try_from(value.len()) else {
217 return false;
218 };
219 let Some(total_len) = paged_cell_len(key.len(), value.len()) else {
220 return false;
221 };
222 let offset = offset as usize;
223 if offset + total_len > PAGED_PAGE_SIZE {
224 return false;
225 }
226
227 page[offset..offset + 2].copy_from_slice(&key_len.to_le_bytes());
228 page[offset + 2..offset + 6].copy_from_slice(&value_len.to_le_bytes());
229 page[offset + PAGED_CELL_HEADER_SIZE..offset + PAGED_CELL_HEADER_SIZE + key.len()]
230 .copy_from_slice(key);
231 page[offset + PAGED_CELL_HEADER_SIZE + key.len()..offset + total_len].copy_from_slice(value);
232 true
233}
234
235pub const PAGED_ENCRYPTION_MARKER: [u8; 4] = *b"RDBE";
237
238pub const PAGED_ENCRYPTION_MARKER_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 32;
244
245pub const PAGED_ENCRYPTION_SALT_SIZE: usize = 32;
246pub const PAGED_ENCRYPTION_KEY_CHECK_PLAINTEXT_SIZE: usize = 32;
247pub const PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE: usize = 60;
248pub const PAGED_ENCRYPTION_HEADER_SIZE: usize =
249 PAGED_ENCRYPTION_SALT_SIZE + PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE;
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct PagedEncryptionHeader {
253 pub salt: [u8; PAGED_ENCRYPTION_SALT_SIZE],
254 pub key_check: Vec<u8>,
255}
256
257pub fn encode_paged_encryption_header(header: &PagedEncryptionHeader) -> Vec<u8> {
258 let mut out = Vec::with_capacity(PAGED_ENCRYPTION_HEADER_SIZE);
259 out.extend_from_slice(&header.salt);
260 out.extend_from_slice(&header.key_check);
261 out
262}
263
264pub fn decode_paged_encryption_header(
265 data: &[u8],
266) -> Result<PagedEncryptionHeader, DatabaseHeaderError> {
267 ensure_len(data, PAGED_ENCRYPTION_HEADER_SIZE)?;
268 let mut salt = [0u8; PAGED_ENCRYPTION_SALT_SIZE];
269 salt.copy_from_slice(&data[..PAGED_ENCRYPTION_SALT_SIZE]);
270 let key_check = data[PAGED_ENCRYPTION_SALT_SIZE..PAGED_ENCRYPTION_HEADER_SIZE].to_vec();
271 Ok(PagedEncryptionHeader { salt, key_check })
272}
273
274pub fn paged_encryption_marker_present(page: &[u8]) -> bool {
275 page.get(PAGED_ENCRYPTION_MARKER_OFFSET..PAGED_ENCRYPTION_MARKER_OFFSET + 4)
276 == Some(&PAGED_ENCRYPTION_MARKER)
277}
278
279pub fn paged_encryption_header_bytes(page: &[u8]) -> Option<&[u8]> {
280 let start = PAGED_ENCRYPTION_MARKER_OFFSET + PAGED_ENCRYPTION_MARKER.len();
281 page.get(start..start + PAGED_ENCRYPTION_HEADER_SIZE)
282}
283
284pub fn write_paged_encryption_marker_and_header(
285 page: &mut [u8],
286 header_bytes: &[u8],
287) -> Result<(), DatabaseHeaderError> {
288 let marker_end = PAGED_ENCRYPTION_MARKER_OFFSET + PAGED_ENCRYPTION_MARKER.len();
289 let header_end = marker_end + header_bytes.len();
290 ensure_len(page, header_end)?;
291 page[PAGED_ENCRYPTION_MARKER_OFFSET..marker_end].copy_from_slice(&PAGED_ENCRYPTION_MARKER);
292 page[marker_end..header_end].copy_from_slice(header_bytes);
293 Ok(())
294}
295
296const DB_MAGIC_OFFSET: usize = PAGED_PAGE_HEADER_SIZE;
297const DB_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 4;
298const DB_PAGE_SIZE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 8;
299const DB_PAGE_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 12;
300const DB_FREELIST_HEAD_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 16;
301const DB_SCHEMA_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 20;
302const DB_CHECKPOINT_LSN_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 24;
303const DB_PHYSICAL_FORMAT_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 32;
304const DB_PHYSICAL_SEQUENCE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 36;
305const DB_MANIFEST_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 44;
306const DB_MANIFEST_OLDEST_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 52;
307const DB_FREE_SET_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 60;
308const DB_MANIFEST_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 68;
309const DB_MANIFEST_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 72;
310const DB_COLLECTION_ROOTS_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 80;
311const DB_COLLECTION_ROOTS_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 84;
312const DB_COLLECTION_ROOT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 92;
313const DB_SNAPSHOT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 96;
314const DB_INDEX_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 100;
315const DB_CATALOG_COLLECTION_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 104;
316const DB_CATALOG_TOTAL_ENTITIES_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 108;
317const DB_EXPORT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 116;
318const DB_GRAPH_PROJECTION_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 120;
319const DB_ANALYTICS_JOB_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 124;
320const DB_MANIFEST_EVENT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 128;
321const DB_REGISTRY_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 132;
322const DB_REGISTRY_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 136;
323const DB_RECOVERY_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 144;
324const DB_RECOVERY_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 148;
325const DB_CATALOG_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 156;
326const DB_CATALOG_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 160;
327const DB_METADATA_STATE_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 168;
328const DB_METADATA_STATE_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 172;
329const DB_VECTOR_ARTIFACT_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 180;
330const DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 184;
331const DB_CHECKPOINT_IN_PROGRESS_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 192;
332const DB_CHECKPOINT_TARGET_LSN_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 193;
333const DB_HEADER_MIN_LEN: usize = DB_CHECKPOINT_TARGET_LSN_OFFSET + 8;
334
335pub const DWB_MAGIC: [u8; 4] = [0x52, 0x44, 0x44, 0x57];
337pub const PAGED_DWB_HEADER_SIZE: usize = 12;
338pub const PAGED_DWB_ENTRY_HEADER_SIZE: usize = 4;
339pub const PAGED_DWB_ENTRY_SIZE: usize = PAGED_DWB_ENTRY_HEADER_SIZE + PAGED_PAGE_SIZE;
340
341#[derive(Debug, Clone, PartialEq, Eq)]
342pub struct PagedDwbEntry {
343 pub page_id: u32,
344 pub page: [u8; PAGED_PAGE_SIZE],
345}
346
347#[derive(Debug, Clone, PartialEq, Eq)]
348pub enum PagedDwbFrameError {
349 ShortHeader { got: usize },
350 InvalidMagic,
351 IncompleteFrame { expected: usize, got: usize },
352 ChecksumMismatch { expected: u32, actual: u32 },
353}
354
355impl fmt::Display for PagedDwbFrameError {
356 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 match self {
358 Self::ShortHeader { got } => write!(f, "DWB frame too short: got {got} bytes"),
359 Self::InvalidMagic => write!(f, "invalid DWB frame magic"),
360 Self::IncompleteFrame { expected, got } => {
361 write!(
362 f,
363 "incomplete DWB frame: expected {expected} bytes, got {got}"
364 )
365 }
366 Self::ChecksumMismatch { expected, actual } => write!(
367 f,
368 "DWB frame checksum mismatch: expected {expected}, actual {actual}"
369 ),
370 }
371 }
372}
373
374impl std::error::Error for PagedDwbFrameError {}
375
376pub fn encode_paged_dwb_frame<'a>(
377 pages: impl IntoIterator<Item = (u32, &'a [u8; PAGED_PAGE_SIZE])>,
378) -> Vec<u8> {
379 let pages: Vec<_> = pages.into_iter().collect();
380 let total = PAGED_DWB_HEADER_SIZE + pages.len() * PAGED_DWB_ENTRY_SIZE;
381 let mut out = Vec::with_capacity(total);
382
383 out.extend_from_slice(&DWB_MAGIC);
384 out.extend_from_slice(&(pages.len() as u32).to_le_bytes());
385 out.extend_from_slice(&[0u8; 4]);
386
387 for (page_id, page) in pages {
388 out.extend_from_slice(&page_id.to_le_bytes());
389 out.extend_from_slice(page);
390 }
391
392 let checksum = crc32(&out[PAGED_DWB_HEADER_SIZE..]);
393 out[8..12].copy_from_slice(&checksum.to_le_bytes());
394 out
395}
396
397pub fn decode_paged_dwb_frame(bytes: &[u8]) -> Result<Vec<PagedDwbEntry>, PagedDwbFrameError> {
398 if bytes.len() < PAGED_DWB_HEADER_SIZE {
399 return Err(PagedDwbFrameError::ShortHeader { got: bytes.len() });
400 }
401 if bytes[0..4] != DWB_MAGIC {
402 return Err(PagedDwbFrameError::InvalidMagic);
403 }
404
405 let count = u32::from_le_bytes(bytes[4..8].try_into().expect("len checked")) as usize;
406 let stored_checksum = u32::from_le_bytes(bytes[8..12].try_into().expect("len checked"));
407 let expected_len = PAGED_DWB_HEADER_SIZE + count * PAGED_DWB_ENTRY_SIZE;
408 if bytes.len() < expected_len {
409 return Err(PagedDwbFrameError::IncompleteFrame {
410 expected: expected_len,
411 got: bytes.len(),
412 });
413 }
414
415 let actual_checksum = crc32(&bytes[PAGED_DWB_HEADER_SIZE..expected_len]);
416 if actual_checksum != stored_checksum {
417 return Err(PagedDwbFrameError::ChecksumMismatch {
418 expected: stored_checksum,
419 actual: actual_checksum,
420 });
421 }
422
423 let mut offset = PAGED_DWB_HEADER_SIZE;
424 let mut entries = Vec::with_capacity(count);
425 for _ in 0..count {
426 let page_id =
427 u32::from_le_bytes(bytes[offset..offset + 4].try_into().expect("len checked"));
428 offset += PAGED_DWB_ENTRY_HEADER_SIZE;
429 let mut page = [0u8; PAGED_PAGE_SIZE];
430 page.copy_from_slice(&bytes[offset..offset + PAGED_PAGE_SIZE]);
431 offset += PAGED_PAGE_SIZE;
432 entries.push(PagedDwbEntry { page_id, page });
433 }
434 Ok(entries)
435}
436
437fn crc32(data: &[u8]) -> u32 {
438 crc32fast::hash(data)
439}
440
441pub const COLUMN_BLOCK_MAGIC: [u8; 4] = *b"RDCC";
443
444pub const COLUMN_BLOCK_VERSION_V1: u16 = 1;
446
447pub const BLOOM_SEGMENT_MAGIC: u8 = 0xBF;
449
450pub const VECTOR_BTREE_FORMAT_VERSION_V1: u16 = 1;
452
453pub const VECTOR_BTREE_FORMAT_VERSION_V2: u16 = 2;
455
456pub const VECTOR_BTREE_FORMAT_VERSION: u16 = VECTOR_BTREE_FORMAT_VERSION_V2;
458
459#[derive(Debug, Clone, PartialEq, Eq)]
461pub struct DatabaseHeader {
462 pub version: u32,
463 pub page_size: u32,
464 pub page_count: u32,
465 pub freelist_head: u32,
466 pub schema_version: u32,
467 pub checkpoint_lsn: u64,
468 pub checkpoint_in_progress: bool,
469 pub checkpoint_target_lsn: u64,
470 pub physical: PhysicalFileHeader,
471}
472
473impl Default for DatabaseHeader {
474 fn default() -> Self {
475 Self {
476 version: PAGE_FILE_VERSION,
477 page_size: PAGED_PAGE_SIZE as u32,
478 page_count: 1,
479 freelist_head: 0,
480 schema_version: 0,
481 checkpoint_lsn: 0,
482 checkpoint_in_progress: false,
483 checkpoint_target_lsn: 0,
484 physical: PhysicalFileHeader::default(),
485 }
486 }
487}
488
489#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
494pub struct PhysicalFileHeader {
495 pub format_version: u32,
496 pub sequence: u64,
497 pub manifest_oldest_root: u64,
498 pub manifest_root: u64,
499 pub free_set_root: u64,
500 pub manifest_page: u32,
501 pub manifest_checksum: u64,
502 pub collection_roots_page: u32,
503 pub collection_roots_checksum: u64,
504 pub collection_root_count: u32,
505 pub snapshot_count: u32,
506 pub index_count: u32,
507 pub catalog_collection_count: u32,
508 pub catalog_total_entities: u64,
509 pub export_count: u32,
510 pub graph_projection_count: u32,
511 pub analytics_job_count: u32,
512 pub manifest_event_count: u32,
513 pub registry_page: u32,
514 pub registry_checksum: u64,
515 pub recovery_page: u32,
516 pub recovery_checksum: u64,
517 pub catalog_page: u32,
518 pub catalog_checksum: u64,
519 pub metadata_state_page: u32,
520 pub metadata_state_checksum: u64,
521 pub vector_artifact_page: u32,
522 pub vector_artifact_checksum: u64,
523}
524
525#[derive(Debug, Clone, PartialEq, Eq)]
526pub enum DatabaseHeaderError {
527 ShortPage { need: usize, got: usize },
528 InvalidMagic,
529 UnsupportedPageSize(u32),
530 UnsupportedDatabaseVersion { file_version: u32, supported: u32 },
531}
532
533impl fmt::Display for DatabaseHeaderError {
534 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535 match self {
536 Self::ShortPage { need, got } => {
537 write!(f, "database header page too short: need {need} bytes, got {got}")
538 }
539 Self::InvalidMagic => write!(f, "invalid database header magic"),
540 Self::UnsupportedPageSize(size) => write!(f, "Unsupported page size: {size}"),
541 Self::UnsupportedDatabaseVersion {
542 file_version,
543 supported,
544 } => write!(
545 f,
546 "Unsupported database version: file version {file_version} is newer than supported {supported}"
547 ),
548 }
549 }
550}
551
552impl std::error::Error for DatabaseHeaderError {}
553
554pub fn database_header_magic_matches(page: &[u8]) -> bool {
555 page.get(DB_MAGIC_OFFSET..DB_MAGIC_OFFSET + PAGE_FILE_MAGIC.len()) == Some(&PAGE_FILE_MAGIC)
556}
557
558pub fn init_database_header_page(
559 page: &mut [u8],
560 page_count: u32,
561) -> Result<(), DatabaseHeaderError> {
562 encode_database_header(
563 page,
564 &DatabaseHeader {
565 page_count,
566 ..DatabaseHeader::default()
567 },
568 )
569}
570
571pub fn database_header_page_count(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
572 read_u32(page, DB_PAGE_COUNT_OFFSET)
573}
574
575pub fn set_database_header_version(
576 page: &mut [u8],
577 version: u32,
578) -> Result<(), DatabaseHeaderError> {
579 write_u32(page, DB_VERSION_OFFSET, version)
580}
581
582pub fn set_database_header_page_count(
583 page: &mut [u8],
584 page_count: u32,
585) -> Result<(), DatabaseHeaderError> {
586 write_u32(page, DB_PAGE_COUNT_OFFSET, page_count)
587}
588
589pub fn database_header_freelist_head(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
590 read_u32(page, DB_FREELIST_HEAD_OFFSET)
591}
592
593pub fn set_database_header_freelist_head(
594 page: &mut [u8],
595 page_id: u32,
596) -> Result<(), DatabaseHeaderError> {
597 write_u32(page, DB_FREELIST_HEAD_OFFSET, page_id)
598}
599
600pub fn database_header_page_size(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
601 read_u32(page, DB_PAGE_SIZE_OFFSET)
602}
603
604pub fn decode_database_header(page: &[u8]) -> Result<DatabaseHeader, DatabaseHeaderError> {
605 ensure_len(page, DB_HEADER_MIN_LEN)?;
606 if !database_header_magic_matches(page) {
607 return Err(DatabaseHeaderError::InvalidMagic);
608 }
609
610 let version = read_u32(page, DB_VERSION_OFFSET)?;
611 let page_size = read_u32(page, DB_PAGE_SIZE_OFFSET)?;
612 if page_size != PAGED_PAGE_SIZE as u32 {
613 return Err(DatabaseHeaderError::UnsupportedPageSize(page_size));
614 }
615 if version > PAGE_FILE_VERSION {
616 return Err(DatabaseHeaderError::UnsupportedDatabaseVersion {
617 file_version: version,
618 supported: PAGE_FILE_VERSION,
619 });
620 }
621
622 Ok(DatabaseHeader {
623 version,
624 page_size,
625 page_count: read_u32(page, DB_PAGE_COUNT_OFFSET)?,
626 freelist_head: read_u32(page, DB_FREELIST_HEAD_OFFSET)?,
627 schema_version: read_u32(page, DB_SCHEMA_VERSION_OFFSET)?,
628 checkpoint_lsn: read_u64(page, DB_CHECKPOINT_LSN_OFFSET)?,
629 checkpoint_in_progress: page[DB_CHECKPOINT_IN_PROGRESS_OFFSET] != 0,
630 checkpoint_target_lsn: read_u64(page, DB_CHECKPOINT_TARGET_LSN_OFFSET)?,
631 physical: PhysicalFileHeader {
632 format_version: read_u32(page, DB_PHYSICAL_FORMAT_VERSION_OFFSET)?,
633 sequence: read_u64(page, DB_PHYSICAL_SEQUENCE_OFFSET)?,
634 manifest_oldest_root: read_u64(page, DB_MANIFEST_OLDEST_ROOT_OFFSET)?,
635 manifest_root: read_u64(page, DB_MANIFEST_ROOT_OFFSET)?,
636 free_set_root: read_u64(page, DB_FREE_SET_ROOT_OFFSET)?,
637 manifest_page: read_u32(page, DB_MANIFEST_PAGE_OFFSET)?,
638 manifest_checksum: read_u64(page, DB_MANIFEST_CHECKSUM_OFFSET)?,
639 collection_roots_page: read_u32(page, DB_COLLECTION_ROOTS_PAGE_OFFSET)?,
640 collection_roots_checksum: read_u64(page, DB_COLLECTION_ROOTS_CHECKSUM_OFFSET)?,
641 collection_root_count: read_u32(page, DB_COLLECTION_ROOT_COUNT_OFFSET)?,
642 snapshot_count: read_u32(page, DB_SNAPSHOT_COUNT_OFFSET)?,
643 index_count: read_u32(page, DB_INDEX_COUNT_OFFSET)?,
644 catalog_collection_count: read_u32(page, DB_CATALOG_COLLECTION_COUNT_OFFSET)?,
645 catalog_total_entities: read_u64(page, DB_CATALOG_TOTAL_ENTITIES_OFFSET)?,
646 export_count: read_u32(page, DB_EXPORT_COUNT_OFFSET)?,
647 graph_projection_count: read_u32(page, DB_GRAPH_PROJECTION_COUNT_OFFSET)?,
648 analytics_job_count: read_u32(page, DB_ANALYTICS_JOB_COUNT_OFFSET)?,
649 manifest_event_count: read_u32(page, DB_MANIFEST_EVENT_COUNT_OFFSET)?,
650 registry_page: read_u32(page, DB_REGISTRY_PAGE_OFFSET)?,
651 registry_checksum: read_u64(page, DB_REGISTRY_CHECKSUM_OFFSET)?,
652 recovery_page: read_u32(page, DB_RECOVERY_PAGE_OFFSET)?,
653 recovery_checksum: read_u64(page, DB_RECOVERY_CHECKSUM_OFFSET)?,
654 catalog_page: read_u32(page, DB_CATALOG_PAGE_OFFSET)?,
655 catalog_checksum: read_u64(page, DB_CATALOG_CHECKSUM_OFFSET)?,
656 metadata_state_page: read_u32(page, DB_METADATA_STATE_PAGE_OFFSET)?,
657 metadata_state_checksum: read_u64(page, DB_METADATA_STATE_CHECKSUM_OFFSET)?,
658 vector_artifact_page: read_u32(page, DB_VECTOR_ARTIFACT_PAGE_OFFSET)?,
659 vector_artifact_checksum: read_u64(page, DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET)?,
660 },
661 })
662}
663
664pub fn encode_database_header(
665 page: &mut [u8],
666 header: &DatabaseHeader,
667) -> Result<(), DatabaseHeaderError> {
668 ensure_len(page, DB_HEADER_MIN_LEN)?;
669 page[DB_MAGIC_OFFSET..DB_MAGIC_OFFSET + PAGE_FILE_MAGIC.len()]
670 .copy_from_slice(&PAGE_FILE_MAGIC);
671 write_u32(page, DB_VERSION_OFFSET, header.version)?;
672 write_u32(page, DB_PAGE_SIZE_OFFSET, header.page_size)?;
673 write_u32(page, DB_PAGE_COUNT_OFFSET, header.page_count)?;
674 write_u32(page, DB_FREELIST_HEAD_OFFSET, header.freelist_head)?;
675 write_u32(page, DB_SCHEMA_VERSION_OFFSET, header.schema_version)?;
676 write_u64(page, DB_CHECKPOINT_LSN_OFFSET, header.checkpoint_lsn)?;
677 write_u32(
678 page,
679 DB_PHYSICAL_FORMAT_VERSION_OFFSET,
680 header.physical.format_version,
681 )?;
682 write_u64(page, DB_PHYSICAL_SEQUENCE_OFFSET, header.physical.sequence)?;
683 write_u64(page, DB_MANIFEST_ROOT_OFFSET, header.physical.manifest_root)?;
684 write_u64(
685 page,
686 DB_MANIFEST_OLDEST_ROOT_OFFSET,
687 header.physical.manifest_oldest_root,
688 )?;
689 write_u64(page, DB_FREE_SET_ROOT_OFFSET, header.physical.free_set_root)?;
690 write_u32(page, DB_MANIFEST_PAGE_OFFSET, header.physical.manifest_page)?;
691 write_u64(
692 page,
693 DB_MANIFEST_CHECKSUM_OFFSET,
694 header.physical.manifest_checksum,
695 )?;
696 write_u32(
697 page,
698 DB_COLLECTION_ROOTS_PAGE_OFFSET,
699 header.physical.collection_roots_page,
700 )?;
701 write_u64(
702 page,
703 DB_COLLECTION_ROOTS_CHECKSUM_OFFSET,
704 header.physical.collection_roots_checksum,
705 )?;
706 write_u32(
707 page,
708 DB_COLLECTION_ROOT_COUNT_OFFSET,
709 header.physical.collection_root_count,
710 )?;
711 write_u32(
712 page,
713 DB_SNAPSHOT_COUNT_OFFSET,
714 header.physical.snapshot_count,
715 )?;
716 write_u32(page, DB_INDEX_COUNT_OFFSET, header.physical.index_count)?;
717 write_u32(
718 page,
719 DB_CATALOG_COLLECTION_COUNT_OFFSET,
720 header.physical.catalog_collection_count,
721 )?;
722 write_u64(
723 page,
724 DB_CATALOG_TOTAL_ENTITIES_OFFSET,
725 header.physical.catalog_total_entities,
726 )?;
727 write_u32(page, DB_EXPORT_COUNT_OFFSET, header.physical.export_count)?;
728 write_u32(
729 page,
730 DB_GRAPH_PROJECTION_COUNT_OFFSET,
731 header.physical.graph_projection_count,
732 )?;
733 write_u32(
734 page,
735 DB_ANALYTICS_JOB_COUNT_OFFSET,
736 header.physical.analytics_job_count,
737 )?;
738 write_u32(
739 page,
740 DB_MANIFEST_EVENT_COUNT_OFFSET,
741 header.physical.manifest_event_count,
742 )?;
743 write_u32(page, DB_REGISTRY_PAGE_OFFSET, header.physical.registry_page)?;
744 write_u64(
745 page,
746 DB_REGISTRY_CHECKSUM_OFFSET,
747 header.physical.registry_checksum,
748 )?;
749 write_u32(page, DB_RECOVERY_PAGE_OFFSET, header.physical.recovery_page)?;
750 write_u64(
751 page,
752 DB_RECOVERY_CHECKSUM_OFFSET,
753 header.physical.recovery_checksum,
754 )?;
755 write_u32(page, DB_CATALOG_PAGE_OFFSET, header.physical.catalog_page)?;
756 write_u64(
757 page,
758 DB_CATALOG_CHECKSUM_OFFSET,
759 header.physical.catalog_checksum,
760 )?;
761 write_u32(
762 page,
763 DB_METADATA_STATE_PAGE_OFFSET,
764 header.physical.metadata_state_page,
765 )?;
766 write_u64(
767 page,
768 DB_METADATA_STATE_CHECKSUM_OFFSET,
769 header.physical.metadata_state_checksum,
770 )?;
771 write_u32(
772 page,
773 DB_VECTOR_ARTIFACT_PAGE_OFFSET,
774 header.physical.vector_artifact_page,
775 )?;
776 write_u64(
777 page,
778 DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET,
779 header.physical.vector_artifact_checksum,
780 )?;
781 page[DB_CHECKPOINT_IN_PROGRESS_OFFSET] = if header.checkpoint_in_progress { 1 } else { 0 };
782 write_u64(
783 page,
784 DB_CHECKPOINT_TARGET_LSN_OFFSET,
785 header.checkpoint_target_lsn,
786 )
787}
788
789fn ensure_len(bytes: &[u8], need: usize) -> Result<(), DatabaseHeaderError> {
790 if bytes.len() < need {
791 return Err(DatabaseHeaderError::ShortPage {
792 need,
793 got: bytes.len(),
794 });
795 }
796 Ok(())
797}
798
799fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, DatabaseHeaderError> {
800 ensure_len(bytes, offset + 4)?;
801 Ok(u32::from_le_bytes(
802 bytes[offset..offset + 4].try_into().expect("len checked"),
803 ))
804}
805
806fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, DatabaseHeaderError> {
807 ensure_len(bytes, offset + 2)?;
808 Ok(u16::from_le_bytes(
809 bytes[offset..offset + 2].try_into().expect("len checked"),
810 ))
811}
812
813fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, DatabaseHeaderError> {
814 ensure_len(bytes, offset + 8)?;
815 Ok(u64::from_le_bytes(
816 bytes[offset..offset + 8].try_into().expect("len checked"),
817 ))
818}
819
820fn write_u32(bytes: &mut [u8], offset: usize, value: u32) -> Result<(), DatabaseHeaderError> {
821 ensure_len(bytes, offset + 4)?;
822 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
823 Ok(())
824}
825
826fn write_u16(bytes: &mut [u8], offset: usize, value: u16) -> Result<(), DatabaseHeaderError> {
827 ensure_len(bytes, offset + 2)?;
828 bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
829 Ok(())
830}
831
832fn write_u64(bytes: &mut [u8], offset: usize, value: u64) -> Result<(), DatabaseHeaderError> {
833 ensure_len(bytes, offset + 8)?;
834 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
835 Ok(())
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841
842 #[test]
843 fn paged_page_header_round_trips_raw_layout() {
844 let header = PagedPageHeader {
845 page_type: 13,
846 flags: 0b1010_0001,
847 cell_count: 42,
848 free_start: 44,
849 free_end: 16_000,
850 page_id: 99,
851 parent_id: 88,
852 right_child: 77,
853 lsn: 66,
854 checksum: 55,
855 };
856
857 let encoded = encode_paged_page_header(&header);
858
859 assert_eq!(encoded.len(), PAGED_PAGE_HEADER_SIZE);
860 assert_eq!(decode_paged_page_header(&encoded), header);
861 }
862
863 #[test]
864 fn paged_page_header_accessors_update_expected_offsets() {
865 let mut page = [0u8; PAGED_PAGE_SIZE];
866 page[0] = 13;
867
868 set_paged_page_cell_count(&mut page, 2);
869 set_paged_page_free_start(&mut page, 34);
870 set_paged_page_free_end(&mut page, 4096);
871 set_paged_page_parent_id(&mut page, 7);
872 set_paged_page_right_child(&mut page, 8);
873 set_paged_page_lsn(&mut page, 9);
874 set_paged_page_checksum(&mut page, 10);
875
876 assert_eq!(paged_page_type(&page), 13);
877 assert_eq!(paged_page_cell_count(&page), 2);
878 assert_eq!(paged_page_free_start(&page), 34);
879 assert_eq!(paged_page_free_end(&page), 4096);
880 assert_eq!(paged_page_parent_id(&page), 7);
881 assert_eq!(paged_page_right_child(&page), 8);
882 assert_eq!(paged_page_lsn(&page), 9);
883 assert_eq!(paged_page_checksum(&page), 10);
884 clear_paged_page_checksum(&mut page);
885 assert_eq!(paged_page_checksum(&page), 0);
886 }
887
888 #[test]
889 fn paged_cell_helpers_round_trip_pointer_and_payload() {
890 let mut page = [0u8; PAGED_PAGE_SIZE];
891 let pointer = (PAGED_PAGE_SIZE - 14) as u16;
892
893 assert!(write_paged_cell(&mut page, pointer, b"key", b"value"));
894 assert!(set_paged_cell_pointer(&mut page, 0, pointer));
895
896 assert_eq!(paged_cell_pointer(&page, 0), Some(pointer));
897 let cell = paged_cell_bytes(&page, pointer).expect("cell bytes");
898 let (key, value) = paged_cell_key_value(cell).expect("key value");
899
900 assert_eq!(key, b"key");
901 assert_eq!(value, b"value");
902 assert_eq!(paged_cell_total_len(&page, pointer), Some(14));
903 }
904
905 #[test]
906 fn database_header_field_helpers_preserve_page_zero_offsets() {
907 let mut page = vec![0u8; PAGED_PAGE_SIZE];
908
909 init_database_header_page(&mut page, 7).expect("init database header page");
910 set_database_header_version(&mut page, PAGE_FILE_VERSION + 1).expect("set version");
911 set_database_header_page_count(&mut page, 9).expect("set page count");
912 set_database_header_freelist_head(&mut page, 4).expect("set freelist head");
913
914 assert!(database_header_magic_matches(&page));
915 assert!(matches!(
916 decode_database_header(&page),
917 Err(DatabaseHeaderError::UnsupportedDatabaseVersion { .. })
918 ));
919 set_database_header_version(&mut page, PAGE_FILE_VERSION).expect("restore version");
920 assert_eq!(
921 database_header_page_size(&page).unwrap(),
922 PAGED_PAGE_SIZE as u32
923 );
924 assert_eq!(database_header_page_count(&page).unwrap(), 9);
925 assert_eq!(database_header_freelist_head(&page).unwrap(), 4);
926 }
927
928 #[test]
929 fn paged_dwb_frame_round_trips_entries_and_validates_checksum() {
930 let mut page = [0u8; PAGED_PAGE_SIZE];
931 page[0] = 7;
932 let frame = encode_paged_dwb_frame([(42, &page)]);
933
934 let entries = decode_paged_dwb_frame(&frame).expect("decode DWB frame");
935 assert_eq!(entries.len(), 1);
936 assert_eq!(entries[0].page_id, 42);
937 assert_eq!(entries[0].page, page);
938
939 let mut corrupted = frame;
940 let last = corrupted.len() - 1;
941 corrupted[last] ^= 0xFF;
942 assert!(matches!(
943 decode_paged_dwb_frame(&corrupted),
944 Err(PagedDwbFrameError::ChecksumMismatch { .. })
945 ));
946 }
947
948 #[test]
949 fn paged_encryption_header_round_trips_marker_and_raw_bytes() {
950 let header = PagedEncryptionHeader {
951 salt: [7u8; PAGED_ENCRYPTION_SALT_SIZE],
952 key_check: vec![9u8; PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE],
953 };
954 let mut page = vec![0u8; PAGED_PAGE_SIZE];
955 let bytes = encode_paged_encryption_header(&header);
956
957 write_paged_encryption_marker_and_header(&mut page, &bytes)
958 .expect("write encryption marker");
959
960 assert!(paged_encryption_marker_present(&page));
961 let raw = paged_encryption_header_bytes(&page).expect("header bytes");
962 assert_eq!(decode_paged_encryption_header(raw).unwrap(), header);
963 }
964
965 #[test]
966 fn database_header_round_trips_page_zero_contract() {
967 let header = DatabaseHeader {
968 version: PAGE_FILE_VERSION,
969 page_size: PAGED_PAGE_SIZE as u32,
970 page_count: 42,
971 freelist_head: 7,
972 schema_version: 3,
973 checkpoint_lsn: 99,
974 checkpoint_in_progress: true,
975 checkpoint_target_lsn: 123,
976 physical: PhysicalFileHeader {
977 format_version: 11,
978 sequence: 12,
979 manifest_oldest_root: 13,
980 manifest_root: 14,
981 free_set_root: 15,
982 manifest_page: 16,
983 manifest_checksum: 17,
984 collection_roots_page: 18,
985 collection_roots_checksum: 19,
986 collection_root_count: 20,
987 snapshot_count: 21,
988 index_count: 22,
989 catalog_collection_count: 23,
990 catalog_total_entities: 24,
991 export_count: 25,
992 graph_projection_count: 26,
993 analytics_job_count: 27,
994 manifest_event_count: 28,
995 registry_page: 29,
996 registry_checksum: 30,
997 recovery_page: 31,
998 recovery_checksum: 32,
999 catalog_page: 33,
1000 catalog_checksum: 34,
1001 metadata_state_page: 35,
1002 metadata_state_checksum: 36,
1003 vector_artifact_page: 37,
1004 vector_artifact_checksum: 38,
1005 },
1006 };
1007 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1008
1009 encode_database_header(&mut page, &header).expect("encode header");
1010
1011 assert!(database_header_magic_matches(&page));
1012 let decoded = decode_database_header(&page).expect("decode header");
1013 assert_eq!(decoded, header);
1014 }
1015
1016 #[test]
1017 fn database_header_rejects_newer_versions() {
1018 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1019 let header = DatabaseHeader {
1020 version: PAGE_FILE_VERSION + 1,
1021 ..DatabaseHeader::default()
1022 };
1023 encode_database_header(&mut page, &header).expect("encode header");
1024
1025 assert!(matches!(
1026 decode_database_header(&page),
1027 Err(DatabaseHeaderError::UnsupportedDatabaseVersion { .. })
1028 ));
1029 }
1030}