1use std::fmt;
7
8pub const PAGE_FILE_MAGIC: [u8; 4] = [0x52, 0x44, 0x44, 0x42];
10
11pub const PAGE_DWB_ZONE_FILE_VERSION: u32 = 0x0001_0001;
13pub const PAGE_OWNERSHIP_FILE_VERSION: u32 = 0x0001_0002;
15pub const PAGE_CHECKPOINT_UNDO_FILE_VERSION: u32 = 0x0001_0003;
17pub const PAGE_DISK_RESIDENCY_FILE_VERSION: u32 = 0x0001_0004;
20pub const PAGE_SECONDARY_INDEX_FILE_VERSION: u32 = 0x0001_0005;
23pub const PAGE_RECOVERY_READ_ONLY_FILE_VERSION: u32 = 0x0001_0006;
26pub const PAGE_RESULT_CACHE_FILE_VERSION: u32 = 0x0001_0007;
28pub const PAGE_RESULT_CACHE_DISK_FILE_VERSION: u32 = 0x0001_0008;
30pub const PAGE_REQUIRED_CAPABILITIES_FILE_VERSION: u32 = 0x0001_0009;
32pub const PAGE_FILE_VERSION: u32 = PAGE_REQUIRED_CAPABILITIES_FILE_VERSION;
33
34pub const FILE_CAPABILITY_TEMPERATURE: u64 = 1 << 0;
36pub const FILE_CAPABILITY_DISK_RESIDENCY: u64 = 1 << 1;
38pub const FILE_CAPABILITY_CATALOG_ROOT: u64 = 1 << 2;
40pub const FILE_CAPABILITY_RECOVERY_READ_ONLY: u64 = 1 << 3;
42pub const FILE_CAPABILITY_TREE_PROOFS: u64 = 1 << 4;
44pub const FILE_CAPABILITY_DWB_PUBLICATION: u64 = 1 << 5;
46const FILE_CAPABILITIES_LEGACY_HEADER: u64 =
49 FILE_CAPABILITY_TEMPERATURE | FILE_CAPABILITY_DISK_RESIDENCY;
50pub const FILE_CAPABILITIES_SUPPORTED: u64 = FILE_CAPABILITY_TEMPERATURE
52 | FILE_CAPABILITY_DISK_RESIDENCY
53 | FILE_CAPABILITY_CATALOG_ROOT
54 | FILE_CAPABILITY_RECOVERY_READ_ONLY
55 | FILE_CAPABILITY_TREE_PROOFS
56 | FILE_CAPABILITY_DWB_PUBLICATION;
57
58pub fn page_format_is_disk_resident(version: u32) -> bool {
61 (PAGE_DISK_RESIDENCY_FILE_VERSION..PAGE_REQUIRED_CAPABILITIES_FILE_VERSION).contains(&version)
62 && version != PAGE_RESULT_CACHE_FILE_VERSION
63}
64
65pub const PAGED_PAGE_SIZE: usize = 16_384;
67
68pub const PAGED_PAGE_HEADER_SIZE: usize = 32;
70
71pub const PAGED_CELL_POINTER_SIZE: usize = 2;
72pub const PAGED_CELL_HEADER_SIZE: usize = 6;
73
74#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
76pub struct PagedPageHeader {
77 pub page_type: u8,
78 pub flags: u8,
79 pub cell_count: u16,
80 pub free_start: u16,
81 pub free_end: u16,
82 pub page_id: u32,
83 pub parent_id: u32,
84 pub right_child: u32,
85 pub lsn: u64,
86 pub checksum: u32,
87}
88
89pub fn encode_paged_page_header(header: &PagedPageHeader) -> [u8; PAGED_PAGE_HEADER_SIZE] {
90 let mut buf = [0u8; PAGED_PAGE_HEADER_SIZE];
91 buf[0] = header.page_type;
92 buf[1] = header.flags;
93 buf[2..4].copy_from_slice(&header.cell_count.to_le_bytes());
94 buf[4..6].copy_from_slice(&header.free_start.to_le_bytes());
95 buf[6..8].copy_from_slice(&header.free_end.to_le_bytes());
96 buf[8..12].copy_from_slice(&header.page_id.to_le_bytes());
97 buf[12..16].copy_from_slice(&header.parent_id.to_le_bytes());
98 buf[16..20].copy_from_slice(&header.right_child.to_le_bytes());
99 buf[20..28].copy_from_slice(&header.lsn.to_le_bytes());
100 buf[28..32].copy_from_slice(&header.checksum.to_le_bytes());
101 buf
102}
103
104pub fn decode_paged_page_header(buf: &[u8; PAGED_PAGE_HEADER_SIZE]) -> PagedPageHeader {
105 PagedPageHeader {
106 page_type: buf[0],
107 flags: buf[1],
108 cell_count: u16::from_le_bytes([buf[2], buf[3]]),
109 free_start: u16::from_le_bytes([buf[4], buf[5]]),
110 free_end: u16::from_le_bytes([buf[6], buf[7]]),
111 page_id: u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]),
112 parent_id: u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]),
113 right_child: u32::from_le_bytes([buf[16], buf[17], buf[18], buf[19]]),
114 lsn: u64::from_le_bytes([
115 buf[20], buf[21], buf[22], buf[23], buf[24], buf[25], buf[26], buf[27],
116 ]),
117 checksum: u32::from_le_bytes([buf[28], buf[29], buf[30], buf[31]]),
118 }
119}
120
121pub fn paged_page_type(page: &[u8; PAGED_PAGE_SIZE]) -> u8 {
122 page[0]
123}
124
125pub fn paged_page_id(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
126 read_u32(page, 8).expect("paged page has header")
127}
128
129pub fn paged_page_lsn(page: &[u8; PAGED_PAGE_SIZE]) -> u64 {
130 read_u64(page, 20).expect("paged page has header")
131}
132
133pub fn set_paged_page_lsn(page: &mut [u8; PAGED_PAGE_SIZE], lsn: u64) {
134 write_u64(page, 20, lsn).expect("paged page has header");
135}
136
137pub fn paged_page_cell_count(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
138 read_u16(page, 2).expect("paged page has header")
139}
140
141pub fn set_paged_page_cell_count(page: &mut [u8; PAGED_PAGE_SIZE], count: u16) {
142 write_u16(page, 2, count).expect("paged page has header");
143}
144
145pub fn paged_page_parent_id(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
146 read_u32(page, 12).expect("paged page has header")
147}
148
149pub fn set_paged_page_parent_id(page: &mut [u8; PAGED_PAGE_SIZE], parent_id: u32) {
150 write_u32(page, 12, parent_id).expect("paged page has header");
151}
152
153pub fn paged_page_right_child(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
154 read_u32(page, 16).expect("paged page has header")
155}
156
157pub fn set_paged_page_right_child(page: &mut [u8; PAGED_PAGE_SIZE], child_id: u32) {
158 write_u32(page, 16, child_id).expect("paged page has header");
159}
160
161pub fn paged_page_free_start(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
162 read_u16(page, 4).expect("paged page has header")
163}
164
165pub fn set_paged_page_free_start(page: &mut [u8; PAGED_PAGE_SIZE], offset: u16) {
166 write_u16(page, 4, offset).expect("paged page has header");
167}
168
169pub fn paged_page_free_end(page: &[u8; PAGED_PAGE_SIZE]) -> u16 {
170 read_u16(page, 6).expect("paged page has header")
171}
172
173pub fn set_paged_page_free_end(page: &mut [u8; PAGED_PAGE_SIZE], offset: u16) {
174 write_u16(page, 6, offset).expect("paged page has header");
175}
176
177pub fn paged_page_checksum(page: &[u8; PAGED_PAGE_SIZE]) -> u32 {
178 read_u32(page, 28).expect("paged page has header")
179}
180
181pub fn clear_paged_page_checksum(page: &mut [u8; PAGED_PAGE_SIZE]) {
182 write_u32(page, 28, 0).expect("paged page has header");
183}
184
185pub fn set_paged_page_checksum(page: &mut [u8; PAGED_PAGE_SIZE], checksum: u32) {
186 write_u32(page, 28, checksum).expect("paged page has header");
187}
188
189pub fn paged_cell_pointer_offset(index: usize) -> Option<usize> {
190 let offset = PAGED_PAGE_HEADER_SIZE.checked_add(index.checked_mul(PAGED_CELL_POINTER_SIZE)?)?;
191 (offset + PAGED_CELL_POINTER_SIZE <= PAGED_PAGE_SIZE).then_some(offset)
192}
193
194pub fn paged_cell_pointer(page: &[u8; PAGED_PAGE_SIZE], index: usize) -> Option<u16> {
195 read_u16(page, paged_cell_pointer_offset(index)?).ok()
196}
197
198pub fn set_paged_cell_pointer(
199 page: &mut [u8; PAGED_PAGE_SIZE],
200 index: usize,
201 pointer: u16,
202) -> bool {
203 let Some(offset) = paged_cell_pointer_offset(index) else {
204 return false;
205 };
206 if !paged_cell_pointer_is_valid(pointer) {
207 return false;
208 }
209 write_u16(page, offset, pointer).is_ok()
210}
211
212pub fn paged_cell_pointer_is_valid(pointer: u16) -> bool {
213 let pointer = pointer as usize;
214 (PAGED_PAGE_HEADER_SIZE..PAGED_PAGE_SIZE).contains(&pointer)
215}
216
217pub fn paged_cell_len(key_len: usize, value_len: usize) -> Option<usize> {
218 PAGED_CELL_HEADER_SIZE
219 .checked_add(key_len)?
220 .checked_add(value_len)
221}
222
223pub fn paged_cell_total_len(page: &[u8; PAGED_PAGE_SIZE], pointer: u16) -> Option<usize> {
224 let pointer = pointer as usize;
225 if pointer + PAGED_CELL_HEADER_SIZE > PAGED_PAGE_SIZE {
226 return None;
227 }
228 let key_len = read_u16(page, pointer).ok()? as usize;
229 let value_len = read_u32(page, pointer + 2).ok()? as usize;
230 let total_len = paged_cell_len(key_len, value_len)?;
231 (pointer + total_len <= PAGED_PAGE_SIZE).then_some(total_len)
232}
233
234pub fn paged_cell_bytes(page: &[u8; PAGED_PAGE_SIZE], pointer: u16) -> Option<&[u8]> {
235 let total_len = paged_cell_total_len(page, pointer)?;
236 let pointer = pointer as usize;
237 Some(&page[pointer..pointer + total_len])
238}
239
240pub fn paged_cell_key_value(cell: &[u8]) -> Option<(&[u8], &[u8])> {
241 if cell.len() < PAGED_CELL_HEADER_SIZE {
242 return None;
243 }
244 let key_len = u16::from_le_bytes(cell[0..2].try_into().ok()?) as usize;
245 let value_len = u32::from_le_bytes(cell[2..6].try_into().ok()?) as usize;
246 let total_len = paged_cell_len(key_len, value_len)?;
247 if total_len > cell.len() {
248 return None;
249 }
250 let key_start = PAGED_CELL_HEADER_SIZE;
251 let value_start = key_start + key_len;
252 Some((
253 &cell[key_start..value_start],
254 &cell[value_start..value_start + value_len],
255 ))
256}
257
258pub fn write_paged_cell(
259 page: &mut [u8; PAGED_PAGE_SIZE],
260 offset: u16,
261 key: &[u8],
262 value: &[u8],
263) -> bool {
264 let Ok(key_len) = u16::try_from(key.len()) else {
265 return false;
266 };
267 let Ok(value_len) = u32::try_from(value.len()) else {
268 return false;
269 };
270 let Some(total_len) = paged_cell_len(key.len(), value.len()) else {
271 return false;
272 };
273 let offset = offset as usize;
274 if offset + total_len > PAGED_PAGE_SIZE {
275 return false;
276 }
277
278 page[offset..offset + 2].copy_from_slice(&key_len.to_le_bytes());
279 page[offset + 2..offset + 6].copy_from_slice(&value_len.to_le_bytes());
280 page[offset + PAGED_CELL_HEADER_SIZE..offset + PAGED_CELL_HEADER_SIZE + key.len()]
281 .copy_from_slice(key);
282 page[offset + PAGED_CELL_HEADER_SIZE + key.len()..offset + total_len].copy_from_slice(value);
283 true
284}
285
286pub const PAGED_ENCRYPTION_MARKER: [u8; 4] = *b"RDBE";
288
289pub const PAGED_ENCRYPTION_MARKER_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 32;
295
296pub const PAGED_ENCRYPTION_SALT_SIZE: usize = 32;
297pub const PAGED_ENCRYPTION_KEY_CHECK_PLAINTEXT_SIZE: usize = 32;
298pub const PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE: usize = 60;
299pub const PAGED_ENCRYPTION_HEADER_SIZE: usize =
300 PAGED_ENCRYPTION_SALT_SIZE + PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE;
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub struct PagedEncryptionHeader {
304 pub salt: [u8; PAGED_ENCRYPTION_SALT_SIZE],
305 pub key_check: Vec<u8>,
306}
307
308pub fn encode_paged_encryption_header(header: &PagedEncryptionHeader) -> Vec<u8> {
309 let mut out = Vec::with_capacity(PAGED_ENCRYPTION_HEADER_SIZE);
310 out.extend_from_slice(&header.salt);
311 out.extend_from_slice(&header.key_check);
312 out
313}
314
315pub fn decode_paged_encryption_header(
316 data: &[u8],
317) -> Result<PagedEncryptionHeader, DatabaseHeaderError> {
318 ensure_len(data, PAGED_ENCRYPTION_HEADER_SIZE)?;
319 let mut salt = [0u8; PAGED_ENCRYPTION_SALT_SIZE];
320 salt.copy_from_slice(&data[..PAGED_ENCRYPTION_SALT_SIZE]);
321 let key_check = data[PAGED_ENCRYPTION_SALT_SIZE..PAGED_ENCRYPTION_HEADER_SIZE].to_vec();
322 Ok(PagedEncryptionHeader { salt, key_check })
323}
324
325pub fn paged_encryption_marker_present(page: &[u8]) -> bool {
326 page.get(PAGED_ENCRYPTION_MARKER_OFFSET..PAGED_ENCRYPTION_MARKER_OFFSET + 4)
327 == Some(&PAGED_ENCRYPTION_MARKER)
328}
329
330pub fn paged_encryption_header_bytes(page: &[u8]) -> Option<&[u8]> {
331 let start = PAGED_ENCRYPTION_MARKER_OFFSET + PAGED_ENCRYPTION_MARKER.len();
332 page.get(start..start + PAGED_ENCRYPTION_HEADER_SIZE)
333}
334
335pub fn write_paged_encryption_marker_and_header(
336 page: &mut [u8],
337 header_bytes: &[u8],
338) -> Result<(), DatabaseHeaderError> {
339 let marker_end = PAGED_ENCRYPTION_MARKER_OFFSET + PAGED_ENCRYPTION_MARKER.len();
340 let header_end = marker_end + header_bytes.len();
341 ensure_len(page, header_end)?;
342 page[PAGED_ENCRYPTION_MARKER_OFFSET..marker_end].copy_from_slice(&PAGED_ENCRYPTION_MARKER);
343 page[marker_end..header_end].copy_from_slice(header_bytes);
344 Ok(())
345}
346
347const DB_MAGIC_OFFSET: usize = PAGED_PAGE_HEADER_SIZE;
348const DB_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 4;
349const DB_PAGE_SIZE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 8;
350const DB_PAGE_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 12;
351const DB_FREELIST_HEAD_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 16;
352const DB_SCHEMA_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 20;
353const DB_CHECKPOINT_LSN_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 24;
354const DB_PHYSICAL_FORMAT_VERSION_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 32;
355const DB_PHYSICAL_SEQUENCE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 36;
356const DB_MANIFEST_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 44;
357const DB_MANIFEST_OLDEST_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 52;
358const DB_FREE_SET_ROOT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 60;
359const DB_MANIFEST_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 68;
360const DB_MANIFEST_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 72;
361const DB_COLLECTION_ROOTS_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 80;
362const DB_COLLECTION_ROOTS_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 84;
363const DB_COLLECTION_ROOT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 92;
364const DB_SNAPSHOT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 96;
365const DB_INDEX_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 100;
366const DB_CATALOG_COLLECTION_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 104;
367const DB_CATALOG_TOTAL_ENTITIES_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 108;
368const DB_EXPORT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 116;
369const DB_GRAPH_PROJECTION_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 120;
370const DB_ANALYTICS_JOB_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 124;
371const DB_MANIFEST_EVENT_COUNT_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 128;
372const DB_REGISTRY_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 132;
373const DB_REGISTRY_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 136;
374const DB_RECOVERY_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 144;
375const DB_RECOVERY_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 148;
376const DB_CATALOG_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 156;
377const DB_CATALOG_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 160;
378const DB_METADATA_STATE_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 168;
379const DB_METADATA_STATE_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 172;
380const DB_VECTOR_ARTIFACT_PAGE_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 180;
381const DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 184;
382const DB_CHECKPOINT_IN_PROGRESS_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 192;
383const DB_CHECKPOINT_TARGET_LSN_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 193;
384const DB_REQUIRED_CAPABILITIES_OFFSET: usize = PAGED_PAGE_HEADER_SIZE + 208;
385const DB_HEADER_MIN_LEN: usize = DB_REQUIRED_CAPABILITIES_OFFSET + 8;
386const DB_MANIFEST_REFERENCE_OFFSET: usize = 256;
388const DB_MANIFEST_REFERENCE_END: usize = DB_MANIFEST_REFERENCE_OFFSET + 36;
389const _: () = assert!(DB_MANIFEST_REFERENCE_END < PAGED_SUPERBLOCK_TRAILER_OFFSET);
390const DB_DWB_PUBLICATION_OFFSET: usize = 296;
394const DB_DWB_PUBLICATION_END: usize = DB_DWB_PUBLICATION_OFFSET + 40;
395const _: () = assert!(DB_MANIFEST_REFERENCE_END <= DB_DWB_PUBLICATION_OFFSET);
396const _: () = assert!(DB_DWB_PUBLICATION_END < PAGED_SUPERBLOCK_TRAILER_OFFSET);
397
398#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399#[repr(u32)]
400pub enum DwbPhase {
401 Preparing = 1,
402 Ready = 2,
403 Consumed = 3,
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq)]
408pub struct DwbPublication {
409 pub phase: DwbPhase,
410 pub frame_bytes: u32,
411 pub digest: [u8; 32],
412}
413
414pub fn dwb_frame_digest(bytes: &[u8]) -> [u8; 32] {
415 *blake3::hash(bytes).as_bytes()
416}
417
418pub fn database_header_dwb_publication(
419 page: &[u8],
420) -> Result<Option<DwbPublication>, HeaderPublicationError> {
421 ensure_len(page, DB_DWB_PUBLICATION_END)?;
422 let field = &page[DB_DWB_PUBLICATION_OFFSET..DB_DWB_PUBLICATION_END];
423 if field == [0; 40] {
424 return Ok(None);
425 }
426 let phase = match read_u32(field, 0)? {
427 1 => DwbPhase::Preparing,
428 2 => DwbPhase::Ready,
429 3 => DwbPhase::Consumed,
430 _ => return Err(HeaderPublicationError::InvalidDwbPublication),
431 };
432 let frame_bytes = read_u32(field, 4)?;
433 let digest = field[8..40].try_into().expect("fixed digest field");
434 let count = (frame_bytes as usize).checked_sub(PAGED_DWB_HEADER_SIZE);
435 if digest == [0; 32]
436 || !count.is_some_and(|bytes| {
437 bytes >= PAGED_DWB_ENTRY_SIZE
438 && bytes.is_multiple_of(PAGED_DWB_ENTRY_SIZE)
439 && frame_bytes as usize <= 64 * PAGED_PAGE_SIZE
440 })
441 {
442 return Err(HeaderPublicationError::InvalidDwbPublication);
443 }
444 Ok(Some(DwbPublication {
445 phase,
446 frame_bytes,
447 digest,
448 }))
449}
450
451pub fn set_database_header_dwb_publication(
452 page: &mut [u8],
453 publication: Option<DwbPublication>,
454) -> Result<(), HeaderPublicationError> {
455 ensure_len(page, DB_DWB_PUBLICATION_END)?;
456 let field = &mut page[DB_DWB_PUBLICATION_OFFSET..DB_DWB_PUBLICATION_END];
457 field.fill(0);
458 if let Some(publication) = publication {
459 write_u32(field, 0, publication.phase as u32)?;
460 write_u32(field, 4, publication.frame_bytes)?;
461 field[8..40].copy_from_slice(&publication.digest);
462 database_header_dwb_publication(page)?;
463 }
464 Ok(())
465}
466
467pub const DWB_MAGIC: [u8; 4] = [0x52, 0x44, 0x44, 0x57];
469pub const PAGED_DWB_HEADER_SIZE: usize = 12;
470pub const PAGED_DWB_ENTRY_HEADER_SIZE: usize = 4;
471pub const PAGED_DWB_ENTRY_SIZE: usize = PAGED_DWB_ENTRY_HEADER_SIZE + PAGED_PAGE_SIZE;
472
473#[derive(Debug, Clone, PartialEq, Eq)]
474pub struct PagedDwbEntry {
475 pub page_id: u32,
476 pub page: [u8; PAGED_PAGE_SIZE],
477}
478
479#[derive(Debug, Clone, PartialEq, Eq)]
480pub enum PagedDwbFrameError {
481 ShortHeader { got: usize },
482 InvalidMagic,
483 IncompleteFrame { expected: usize, got: usize },
484 ChecksumMismatch { expected: u32, actual: u32 },
485}
486
487impl fmt::Display for PagedDwbFrameError {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 match self {
490 Self::ShortHeader { got } => write!(f, "DWB frame too short: got {got} bytes"),
491 Self::InvalidMagic => write!(f, "invalid DWB frame magic"),
492 Self::IncompleteFrame { expected, got } => {
493 write!(
494 f,
495 "incomplete DWB frame: expected {expected} bytes, got {got}"
496 )
497 }
498 Self::ChecksumMismatch { expected, actual } => write!(
499 f,
500 "DWB frame checksum mismatch: expected {expected}, actual {actual}"
501 ),
502 }
503 }
504}
505
506impl std::error::Error for PagedDwbFrameError {}
507
508pub fn encode_paged_dwb_header(count: u32, checksum: u32) -> [u8; PAGED_DWB_HEADER_SIZE] {
509 let mut header = [0; PAGED_DWB_HEADER_SIZE];
510 header[..4].copy_from_slice(&DWB_MAGIC);
511 header[4..8].copy_from_slice(&count.to_le_bytes());
512 header[8..12].copy_from_slice(&checksum.to_le_bytes());
513 header
514}
515
516pub fn decode_paged_dwb_header(bytes: &[u8]) -> Result<(u32, u32), PagedDwbFrameError> {
517 if bytes.len() < PAGED_DWB_HEADER_SIZE {
518 return Err(PagedDwbFrameError::ShortHeader { got: bytes.len() });
519 }
520 if bytes[..4] != DWB_MAGIC {
521 return Err(PagedDwbFrameError::InvalidMagic);
522 }
523 Ok((
524 u32::from_le_bytes(bytes[4..8].try_into().expect("header length checked")),
525 u32::from_le_bytes(bytes[8..12].try_into().expect("header length checked")),
526 ))
527}
528
529pub fn encode_paged_dwb_frame<'a>(
530 pages: impl IntoIterator<Item = (u32, &'a [u8; PAGED_PAGE_SIZE])>,
531) -> Vec<u8> {
532 let pages: Vec<_> = pages.into_iter().collect();
533 let total = PAGED_DWB_HEADER_SIZE + pages.len() * PAGED_DWB_ENTRY_SIZE;
534 let mut out = Vec::with_capacity(total);
535
536 out.extend_from_slice(&encode_paged_dwb_header(
537 u32::try_from(pages.len()).expect("DWB count fits u32"),
538 0,
539 ));
540
541 for (page_id, page) in pages {
542 out.extend_from_slice(&page_id.to_le_bytes());
543 out.extend_from_slice(page);
544 }
545
546 let checksum = crc32(&out[PAGED_DWB_HEADER_SIZE..]);
547 out[8..12].copy_from_slice(&checksum.to_le_bytes());
548 out
549}
550
551pub fn decode_paged_dwb_frame(bytes: &[u8]) -> Result<Vec<PagedDwbEntry>, PagedDwbFrameError> {
552 let (count, stored_checksum) = decode_paged_dwb_header(bytes)?;
553 let count = count as usize;
554 let expected_len = PAGED_DWB_HEADER_SIZE + count * PAGED_DWB_ENTRY_SIZE;
555 if bytes.len() < expected_len {
556 return Err(PagedDwbFrameError::IncompleteFrame {
557 expected: expected_len,
558 got: bytes.len(),
559 });
560 }
561
562 let actual_checksum = crc32(&bytes[PAGED_DWB_HEADER_SIZE..expected_len]);
563 if actual_checksum != stored_checksum {
564 return Err(PagedDwbFrameError::ChecksumMismatch {
565 expected: stored_checksum,
566 actual: actual_checksum,
567 });
568 }
569
570 let mut offset = PAGED_DWB_HEADER_SIZE;
571 let mut entries = Vec::with_capacity(count);
572 for _ in 0..count {
573 let page_id =
574 u32::from_le_bytes(bytes[offset..offset + 4].try_into().expect("len checked"));
575 offset += PAGED_DWB_ENTRY_HEADER_SIZE;
576 let mut page = [0u8; PAGED_PAGE_SIZE];
577 page.copy_from_slice(&bytes[offset..offset + PAGED_PAGE_SIZE]);
578 offset += PAGED_PAGE_SIZE;
579 entries.push(PagedDwbEntry { page_id, page });
580 }
581 Ok(entries)
582}
583
584fn crc32(data: &[u8]) -> u32 {
585 crc32fast::hash(data)
586}
587
588pub const COLUMN_BLOCK_MAGIC: [u8; 4] = *b"RDCC";
590
591pub const COLUMN_BLOCK_VERSION_V1: u16 = 1;
593
594pub const BLOOM_SEGMENT_V2_MAGIC: u8 = 0xC0;
596
597pub const VECTOR_BTREE_FORMAT_VERSION_V1: u16 = 1;
599
600pub const VECTOR_BTREE_FORMAT_VERSION_V2: u16 = 2;
602
603pub const VECTOR_BTREE_FORMAT_VERSION: u16 = VECTOR_BTREE_FORMAT_VERSION_V2;
605
606#[derive(Debug, Clone, PartialEq, Eq)]
608pub struct DatabaseHeader {
609 pub version: u32,
610 pub page_size: u32,
611 pub page_count: u32,
612 pub freelist_head: u32,
613 pub schema_version: u32,
614 pub checkpoint_lsn: u64,
615 pub checkpoint_in_progress: bool,
616 pub checkpoint_target_lsn: u64,
617 pub required_capabilities: u64,
619 pub physical: PhysicalFileHeader,
620}
621
622impl DatabaseHeader {
623 pub fn is_recovery_read_only(&self) -> bool {
624 self.version == PAGE_RECOVERY_READ_ONLY_FILE_VERSION
625 || self.required_capabilities & FILE_CAPABILITY_RECOVERY_READ_ONLY != 0
626 }
627
628 pub fn requires_disk_residency(&self) -> bool {
635 if self.version < PAGE_REQUIRED_CAPABILITIES_FILE_VERSION {
636 page_format_is_disk_resident(self.version)
637 } else {
638 self.required_capabilities & FILE_CAPABILITY_DISK_RESIDENCY != 0
639 }
640 }
641}
642
643impl Default for DatabaseHeader {
644 fn default() -> Self {
645 Self {
646 version: PAGE_CHECKPOINT_UNDO_FILE_VERSION,
648 page_size: PAGED_PAGE_SIZE as u32,
649 page_count: 1,
650 freelist_head: 0,
651 schema_version: 0,
652 checkpoint_lsn: 0,
653 checkpoint_in_progress: false,
654 checkpoint_target_lsn: 0,
655 required_capabilities: 0,
656 physical: PhysicalFileHeader::default(),
657 }
658 }
659}
660
661#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
666pub struct PhysicalFileHeader {
667 pub format_version: u32,
668 pub sequence: u64,
669 pub manifest_oldest_root: u64,
670 pub manifest_root: u64,
671 pub free_set_root: u64,
672 pub manifest_page: u32,
673 pub manifest_checksum: u64,
674 pub collection_roots_page: u32,
675 pub collection_roots_checksum: u64,
676 pub collection_root_count: u32,
677 pub snapshot_count: u32,
678 pub index_count: u32,
679 pub catalog_collection_count: u32,
680 pub catalog_total_entities: u64,
681 pub export_count: u32,
682 pub graph_projection_count: u32,
683 pub analytics_job_count: u32,
684 pub manifest_event_count: u32,
685 pub registry_page: u32,
686 pub registry_checksum: u64,
687 pub recovery_page: u32,
688 pub recovery_checksum: u64,
689 pub catalog_page: u32,
690 pub catalog_checksum: u64,
691 pub metadata_state_page: u32,
692 pub metadata_state_checksum: u64,
693 pub vector_artifact_page: u32,
694 pub vector_artifact_checksum: u64,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq)]
698pub enum DatabaseHeaderError {
699 ShortPage { need: usize, got: usize },
700 InvalidMagic,
701 UnsupportedPageSize(u32),
702 UnsupportedDatabaseVersion { file_version: u32, supported: u32 },
703 UnsupportedRequiredCapabilities(u64),
704}
705
706impl fmt::Display for DatabaseHeaderError {
707 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708 match self {
709 Self::ShortPage { need, got } => {
710 write!(f, "database header page too short: need {need} bytes, got {got}")
711 }
712 Self::InvalidMagic => write!(f, "invalid database header magic"),
713 Self::UnsupportedPageSize(size) => write!(f, "Unsupported page size: {size}"),
714 Self::UnsupportedDatabaseVersion {
715 file_version,
716 supported,
717 } => write!(
718 f,
719 "Unsupported database version: file version {file_version} is newer than supported {supported}"
720 ),
721 Self::UnsupportedRequiredCapabilities(bits) => write!(
722 f,
723 "Unsupported required database capabilities: {bits:#018x}"
724 ),
725 }
726 }
727}
728
729impl std::error::Error for DatabaseHeaderError {}
730
731#[derive(Debug, Clone, PartialEq, Eq)]
734#[non_exhaustive]
735pub enum HeaderPublicationError {
736 Header(DatabaseHeaderError),
737 InvalidManifestReference,
738 InvalidDwbPublication,
739}
740
741impl From<DatabaseHeaderError> for HeaderPublicationError {
742 fn from(error: DatabaseHeaderError) -> Self {
743 Self::Header(error)
744 }
745}
746
747impl fmt::Display for HeaderPublicationError {
748 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
749 match self {
750 Self::Header(error) => error.fmt(f),
751 Self::InvalidManifestReference => write!(f, "invalid catalog generation reference"),
752 Self::InvalidDwbPublication => write!(f, "invalid DWB generation publication"),
753 }
754 }
755}
756
757impl std::error::Error for HeaderPublicationError {
758 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
759 match self {
760 Self::Header(error) => Some(error),
761 _ => None,
762 }
763 }
764}
765
766pub fn database_header_magic_matches(page: &[u8]) -> bool {
767 page.get(DB_MAGIC_OFFSET..DB_MAGIC_OFFSET + PAGE_FILE_MAGIC.len()) == Some(&PAGE_FILE_MAGIC)
768}
769
770pub fn init_database_header_page(
771 page: &mut [u8],
772 page_count: u32,
773) -> Result<(), DatabaseHeaderError> {
774 encode_database_header(
775 page,
776 &DatabaseHeader {
777 page_count,
778 ..DatabaseHeader::default()
779 },
780 )
781}
782
783pub fn database_header_page_count(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
784 read_u32(page, DB_PAGE_COUNT_OFFSET)
785}
786
787pub fn set_database_header_version(
788 page: &mut [u8],
789 version: u32,
790) -> Result<(), DatabaseHeaderError> {
791 write_u32(page, DB_VERSION_OFFSET, version)
792}
793
794pub fn set_database_header_page_count(
795 page: &mut [u8],
796 page_count: u32,
797) -> Result<(), DatabaseHeaderError> {
798 write_u32(page, DB_PAGE_COUNT_OFFSET, page_count)
799}
800
801pub fn database_header_freelist_head(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
802 read_u32(page, DB_FREELIST_HEAD_OFFSET)
803}
804
805pub fn set_database_header_freelist_head(
806 page: &mut [u8],
807 page_id: u32,
808) -> Result<(), DatabaseHeaderError> {
809 write_u32(page, DB_FREELIST_HEAD_OFFSET, page_id)
810}
811
812pub fn set_database_header_required_capabilities(
813 page: &mut [u8],
814 capabilities: u64,
815) -> Result<(), DatabaseHeaderError> {
816 write_u64(page, DB_REQUIRED_CAPABILITIES_OFFSET, capabilities)
817}
818
819pub fn set_database_header_manifest_reference(
820 page: &mut [u8],
821 reference: crate::page_proof::PageReference,
822) -> Result<(), HeaderPublicationError> {
823 ensure_len(page, DB_MANIFEST_REFERENCE_END)?;
824 if (reference.page_id == 0) != (reference.digest == [0; 32]) {
825 return Err(HeaderPublicationError::InvalidManifestReference);
826 }
827 crate::page_proof::encode_reference(
828 &mut page[DB_MANIFEST_REFERENCE_OFFSET..DB_MANIFEST_REFERENCE_END],
829 reference,
830 );
831 Ok(())
832}
833
834pub fn database_header_manifest_reference(
838 page: &[u8],
839) -> Result<crate::page_proof::PageReference, HeaderPublicationError> {
840 let header = decode_database_header_fields(page, FILE_CAPABILITIES_SUPPORTED)?;
841 if header.required_capabilities & FILE_CAPABILITY_CATALOG_ROOT == 0 {
842 return Ok(crate::page_proof::PageReference::default());
843 }
844 ensure_len(page, DB_MANIFEST_REFERENCE_END)?;
845 crate::page_proof::decode_reference(
846 &page[DB_MANIFEST_REFERENCE_OFFSET..DB_MANIFEST_REFERENCE_END],
847 )
848 .ok_or(HeaderPublicationError::InvalidManifestReference)
849}
850
851pub fn database_header_page_size(page: &[u8]) -> Result<u32, DatabaseHeaderError> {
852 read_u32(page, DB_PAGE_SIZE_OFFSET)
853}
854
855pub fn decode_database_header(page: &[u8]) -> Result<DatabaseHeader, DatabaseHeaderError> {
858 decode_database_header_fields(page, FILE_CAPABILITIES_LEGACY_HEADER)
859}
860
861pub fn decode_published_database_header(
864 page: &[u8],
865) -> Result<DatabaseHeader, HeaderPublicationError> {
866 let header = decode_database_header_fields(page, FILE_CAPABILITIES_SUPPORTED)?;
867 database_header_manifest_reference(page)?;
868 if header.required_capabilities & FILE_CAPABILITY_DWB_PUBLICATION != 0 {
869 database_header_dwb_publication(page)?;
870 }
871 Ok(header)
872}
873
874fn decode_database_header_fields(
875 page: &[u8],
876 supported_capabilities: u64,
877) -> Result<DatabaseHeader, DatabaseHeaderError> {
878 ensure_len(page, DB_HEADER_MIN_LEN)?;
879 if !database_header_magic_matches(page) {
880 return Err(DatabaseHeaderError::InvalidMagic);
881 }
882
883 let version = read_u32(page, DB_VERSION_OFFSET)?;
884 let page_size = read_u32(page, DB_PAGE_SIZE_OFFSET)?;
885 if page_size != PAGED_PAGE_SIZE as u32 {
886 return Err(DatabaseHeaderError::UnsupportedPageSize(page_size));
887 }
888 if version > PAGE_FILE_VERSION {
889 return Err(DatabaseHeaderError::UnsupportedDatabaseVersion {
890 file_version: version,
891 supported: PAGE_FILE_VERSION,
892 });
893 }
894 let required_capabilities = if version >= PAGE_REQUIRED_CAPABILITIES_FILE_VERSION {
895 read_u64(page, DB_REQUIRED_CAPABILITIES_OFFSET)?
896 } else {
897 0
898 };
899 let unsupported_capabilities = required_capabilities & !supported_capabilities;
900 if unsupported_capabilities != 0 {
901 return Err(DatabaseHeaderError::UnsupportedRequiredCapabilities(
902 unsupported_capabilities,
903 ));
904 }
905 Ok(DatabaseHeader {
906 version,
907 page_size,
908 page_count: read_u32(page, DB_PAGE_COUNT_OFFSET)?,
909 freelist_head: read_u32(page, DB_FREELIST_HEAD_OFFSET)?,
910 schema_version: read_u32(page, DB_SCHEMA_VERSION_OFFSET)?,
911 checkpoint_lsn: read_u64(page, DB_CHECKPOINT_LSN_OFFSET)?,
912 checkpoint_in_progress: page[DB_CHECKPOINT_IN_PROGRESS_OFFSET] != 0,
913 checkpoint_target_lsn: read_u64(page, DB_CHECKPOINT_TARGET_LSN_OFFSET)?,
914 required_capabilities,
915 physical: PhysicalFileHeader {
916 format_version: read_u32(page, DB_PHYSICAL_FORMAT_VERSION_OFFSET)?,
917 sequence: read_u64(page, DB_PHYSICAL_SEQUENCE_OFFSET)?,
918 manifest_oldest_root: read_u64(page, DB_MANIFEST_OLDEST_ROOT_OFFSET)?,
919 manifest_root: read_u64(page, DB_MANIFEST_ROOT_OFFSET)?,
920 free_set_root: read_u64(page, DB_FREE_SET_ROOT_OFFSET)?,
921 manifest_page: read_u32(page, DB_MANIFEST_PAGE_OFFSET)?,
922 manifest_checksum: read_u64(page, DB_MANIFEST_CHECKSUM_OFFSET)?,
923 collection_roots_page: read_u32(page, DB_COLLECTION_ROOTS_PAGE_OFFSET)?,
924 collection_roots_checksum: read_u64(page, DB_COLLECTION_ROOTS_CHECKSUM_OFFSET)?,
925 collection_root_count: read_u32(page, DB_COLLECTION_ROOT_COUNT_OFFSET)?,
926 snapshot_count: read_u32(page, DB_SNAPSHOT_COUNT_OFFSET)?,
927 index_count: read_u32(page, DB_INDEX_COUNT_OFFSET)?,
928 catalog_collection_count: read_u32(page, DB_CATALOG_COLLECTION_COUNT_OFFSET)?,
929 catalog_total_entities: read_u64(page, DB_CATALOG_TOTAL_ENTITIES_OFFSET)?,
930 export_count: read_u32(page, DB_EXPORT_COUNT_OFFSET)?,
931 graph_projection_count: read_u32(page, DB_GRAPH_PROJECTION_COUNT_OFFSET)?,
932 analytics_job_count: read_u32(page, DB_ANALYTICS_JOB_COUNT_OFFSET)?,
933 manifest_event_count: read_u32(page, DB_MANIFEST_EVENT_COUNT_OFFSET)?,
934 registry_page: read_u32(page, DB_REGISTRY_PAGE_OFFSET)?,
935 registry_checksum: read_u64(page, DB_REGISTRY_CHECKSUM_OFFSET)?,
936 recovery_page: read_u32(page, DB_RECOVERY_PAGE_OFFSET)?,
937 recovery_checksum: read_u64(page, DB_RECOVERY_CHECKSUM_OFFSET)?,
938 catalog_page: read_u32(page, DB_CATALOG_PAGE_OFFSET)?,
939 catalog_checksum: read_u64(page, DB_CATALOG_CHECKSUM_OFFSET)?,
940 metadata_state_page: read_u32(page, DB_METADATA_STATE_PAGE_OFFSET)?,
941 metadata_state_checksum: read_u64(page, DB_METADATA_STATE_CHECKSUM_OFFSET)?,
942 vector_artifact_page: read_u32(page, DB_VECTOR_ARTIFACT_PAGE_OFFSET)?,
943 vector_artifact_checksum: read_u64(page, DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET)?,
944 },
945 })
946}
947
948pub fn encode_database_header(
949 page: &mut [u8],
950 header: &DatabaseHeader,
951) -> Result<(), DatabaseHeaderError> {
952 ensure_len(page, DB_HEADER_MIN_LEN)?;
953 page[DB_MAGIC_OFFSET..DB_MAGIC_OFFSET + PAGE_FILE_MAGIC.len()]
954 .copy_from_slice(&PAGE_FILE_MAGIC);
955 write_u32(page, DB_VERSION_OFFSET, header.version)?;
956 write_u32(page, DB_PAGE_SIZE_OFFSET, header.page_size)?;
957 write_u32(page, DB_PAGE_COUNT_OFFSET, header.page_count)?;
958 write_u32(page, DB_FREELIST_HEAD_OFFSET, header.freelist_head)?;
959 write_u32(page, DB_SCHEMA_VERSION_OFFSET, header.schema_version)?;
960 write_u64(page, DB_CHECKPOINT_LSN_OFFSET, header.checkpoint_lsn)?;
961 write_u32(
962 page,
963 DB_PHYSICAL_FORMAT_VERSION_OFFSET,
964 header.physical.format_version,
965 )?;
966 write_u64(page, DB_PHYSICAL_SEQUENCE_OFFSET, header.physical.sequence)?;
967 write_u64(page, DB_MANIFEST_ROOT_OFFSET, header.physical.manifest_root)?;
968 write_u64(
969 page,
970 DB_MANIFEST_OLDEST_ROOT_OFFSET,
971 header.physical.manifest_oldest_root,
972 )?;
973 write_u64(page, DB_FREE_SET_ROOT_OFFSET, header.physical.free_set_root)?;
974 write_u32(page, DB_MANIFEST_PAGE_OFFSET, header.physical.manifest_page)?;
975 write_u64(
976 page,
977 DB_MANIFEST_CHECKSUM_OFFSET,
978 header.physical.manifest_checksum,
979 )?;
980 write_u32(
981 page,
982 DB_COLLECTION_ROOTS_PAGE_OFFSET,
983 header.physical.collection_roots_page,
984 )?;
985 write_u64(
986 page,
987 DB_COLLECTION_ROOTS_CHECKSUM_OFFSET,
988 header.physical.collection_roots_checksum,
989 )?;
990 write_u32(
991 page,
992 DB_COLLECTION_ROOT_COUNT_OFFSET,
993 header.physical.collection_root_count,
994 )?;
995 write_u32(
996 page,
997 DB_SNAPSHOT_COUNT_OFFSET,
998 header.physical.snapshot_count,
999 )?;
1000 write_u32(page, DB_INDEX_COUNT_OFFSET, header.physical.index_count)?;
1001 write_u32(
1002 page,
1003 DB_CATALOG_COLLECTION_COUNT_OFFSET,
1004 header.physical.catalog_collection_count,
1005 )?;
1006 write_u64(
1007 page,
1008 DB_CATALOG_TOTAL_ENTITIES_OFFSET,
1009 header.physical.catalog_total_entities,
1010 )?;
1011 write_u32(page, DB_EXPORT_COUNT_OFFSET, header.physical.export_count)?;
1012 write_u32(
1013 page,
1014 DB_GRAPH_PROJECTION_COUNT_OFFSET,
1015 header.physical.graph_projection_count,
1016 )?;
1017 write_u32(
1018 page,
1019 DB_ANALYTICS_JOB_COUNT_OFFSET,
1020 header.physical.analytics_job_count,
1021 )?;
1022 write_u32(
1023 page,
1024 DB_MANIFEST_EVENT_COUNT_OFFSET,
1025 header.physical.manifest_event_count,
1026 )?;
1027 write_u32(page, DB_REGISTRY_PAGE_OFFSET, header.physical.registry_page)?;
1028 write_u64(
1029 page,
1030 DB_REGISTRY_CHECKSUM_OFFSET,
1031 header.physical.registry_checksum,
1032 )?;
1033 write_u32(page, DB_RECOVERY_PAGE_OFFSET, header.physical.recovery_page)?;
1034 write_u64(
1035 page,
1036 DB_RECOVERY_CHECKSUM_OFFSET,
1037 header.physical.recovery_checksum,
1038 )?;
1039 write_u32(page, DB_CATALOG_PAGE_OFFSET, header.physical.catalog_page)?;
1040 write_u64(
1041 page,
1042 DB_CATALOG_CHECKSUM_OFFSET,
1043 header.physical.catalog_checksum,
1044 )?;
1045 write_u32(
1046 page,
1047 DB_METADATA_STATE_PAGE_OFFSET,
1048 header.physical.metadata_state_page,
1049 )?;
1050 write_u64(
1051 page,
1052 DB_METADATA_STATE_CHECKSUM_OFFSET,
1053 header.physical.metadata_state_checksum,
1054 )?;
1055 write_u32(
1056 page,
1057 DB_VECTOR_ARTIFACT_PAGE_OFFSET,
1058 header.physical.vector_artifact_page,
1059 )?;
1060 write_u64(
1061 page,
1062 DB_VECTOR_ARTIFACT_CHECKSUM_OFFSET,
1063 header.physical.vector_artifact_checksum,
1064 )?;
1065 page[DB_CHECKPOINT_IN_PROGRESS_OFFSET] = if header.checkpoint_in_progress { 1 } else { 0 };
1066 write_u64(
1067 page,
1068 DB_CHECKPOINT_TARGET_LSN_OFFSET,
1069 header.checkpoint_target_lsn,
1070 )?;
1071 write_u64(
1072 page,
1073 DB_REQUIRED_CAPABILITIES_OFFSET,
1074 header.required_capabilities,
1075 )?;
1076 Ok(())
1079}
1080
1081pub const PAGED_SUPERBLOCK_SLOT_SIZE: usize = 4096;
1100
1101pub const PAGED_SUPERBLOCK_SLOT_COUNT: usize = 2;
1103
1104pub const PAGED_SUPERBLOCK_ZONE_SIZE: usize =
1106 PAGED_SUPERBLOCK_SLOT_SIZE * PAGED_SUPERBLOCK_SLOT_COUNT;
1107
1108const PAGED_SUPERBLOCK_MAGIC: [u8; 8] = *b"RDBPSBK1";
1109const PAGED_SUPERBLOCK_SLOT_VERSION: u32 = 1;
1110
1111pub const PAGED_SUPERBLOCK_TRAILER_OFFSET: usize = PAGED_SUPERBLOCK_SLOT_SIZE - 32;
1116const PAGED_SUPERBLOCK_VERSION_OFFSET: usize = PAGED_SUPERBLOCK_TRAILER_OFFSET + 8;
1117const PAGED_SUPERBLOCK_COPY_OFFSET: usize = PAGED_SUPERBLOCK_TRAILER_OFFSET + 12;
1118const PAGED_SUPERBLOCK_GENERATION_OFFSET: usize = PAGED_SUPERBLOCK_TRAILER_OFFSET + 16;
1119const PAGED_SUPERBLOCK_CRC_OFFSET: usize = PAGED_SUPERBLOCK_TRAILER_OFFSET + 24;
1120
1121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1123pub struct PagedSuperblockSelection {
1124 pub copy_index: usize,
1125 pub generation: u64,
1126}
1127
1128pub fn paged_superblock_slot_offset(copy_index: usize) -> u64 {
1130 (copy_index % PAGED_SUPERBLOCK_SLOT_COUNT) as u64 * PAGED_SUPERBLOCK_SLOT_SIZE as u64
1131}
1132
1133pub fn paged_superblock_next_copy(newest: Option<PagedSuperblockSelection>) -> usize {
1136 match newest {
1137 Some(selection) => (selection.copy_index + 1) % PAGED_SUPERBLOCK_SLOT_COUNT,
1138 None => 0,
1139 }
1140}
1141
1142pub fn seal_paged_superblock_slot(
1145 slot: &mut [u8],
1146 copy_index: usize,
1147 generation: u64,
1148) -> Result<(), DatabaseHeaderError> {
1149 ensure_len(slot, PAGED_SUPERBLOCK_SLOT_SIZE)?;
1150 slot[PAGED_SUPERBLOCK_TRAILER_OFFSET..PAGED_SUPERBLOCK_TRAILER_OFFSET + 8]
1151 .copy_from_slice(&PAGED_SUPERBLOCK_MAGIC);
1152 write_u32(
1153 slot,
1154 PAGED_SUPERBLOCK_VERSION_OFFSET,
1155 PAGED_SUPERBLOCK_SLOT_VERSION,
1156 )?;
1157 write_u32(
1158 slot,
1159 PAGED_SUPERBLOCK_COPY_OFFSET,
1160 u32::try_from(copy_index).unwrap_or(u32::MAX),
1161 )?;
1162 write_u64(slot, PAGED_SUPERBLOCK_GENERATION_OFFSET, generation)?;
1163 let crc = paged_superblock_crc(&slot[..PAGED_SUPERBLOCK_CRC_OFFSET]);
1164 write_u32(slot, PAGED_SUPERBLOCK_CRC_OFFSET, crc)?;
1165 write_u32(slot, PAGED_SUPERBLOCK_CRC_OFFSET + 4, 0)
1167}
1168
1169pub fn paged_superblock_slot_generation(slot: &[u8], copy_index: usize) -> Option<u64> {
1172 if slot.len() < PAGED_SUPERBLOCK_SLOT_SIZE {
1173 return None;
1174 }
1175 if slot[PAGED_SUPERBLOCK_TRAILER_OFFSET..PAGED_SUPERBLOCK_TRAILER_OFFSET + 8]
1176 != PAGED_SUPERBLOCK_MAGIC
1177 {
1178 return None;
1179 }
1180 if read_u32(slot, PAGED_SUPERBLOCK_VERSION_OFFSET).ok()? != PAGED_SUPERBLOCK_SLOT_VERSION {
1181 return None;
1182 }
1183 if read_u32(slot, PAGED_SUPERBLOCK_COPY_OFFSET).ok()? != u32::try_from(copy_index).ok()? {
1186 return None;
1187 }
1188 let stored = read_u32(slot, PAGED_SUPERBLOCK_CRC_OFFSET).ok()?;
1189 if paged_superblock_crc(&slot[..PAGED_SUPERBLOCK_CRC_OFFSET]) != stored {
1190 return None;
1191 }
1192 read_u64(slot, PAGED_SUPERBLOCK_GENERATION_OFFSET).ok()
1193}
1194
1195pub fn select_paged_superblock(zone: &[u8]) -> Option<PagedSuperblockSelection> {
1198 (0..PAGED_SUPERBLOCK_SLOT_COUNT)
1199 .filter_map(|copy_index| {
1200 let start = copy_index * PAGED_SUPERBLOCK_SLOT_SIZE;
1201 let slot = zone.get(start..start + PAGED_SUPERBLOCK_SLOT_SIZE)?;
1202 Some(PagedSuperblockSelection {
1203 copy_index,
1204 generation: paged_superblock_slot_generation(slot, copy_index)?,
1205 })
1206 })
1207 .max_by_key(|selection| selection.generation)
1208}
1209
1210fn paged_superblock_crc(bytes: &[u8]) -> u32 {
1211 let mut hasher = crc32fast::Hasher::new();
1212 hasher.update(bytes);
1213 hasher.finalize()
1214}
1215
1216fn ensure_len(bytes: &[u8], need: usize) -> Result<(), DatabaseHeaderError> {
1217 if bytes.len() < need {
1218 return Err(DatabaseHeaderError::ShortPage {
1219 need,
1220 got: bytes.len(),
1221 });
1222 }
1223 Ok(())
1224}
1225
1226fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, DatabaseHeaderError> {
1227 ensure_len(bytes, offset + 4)?;
1228 Ok(u32::from_le_bytes(
1229 bytes[offset..offset + 4].try_into().expect("len checked"),
1230 ))
1231}
1232
1233fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, DatabaseHeaderError> {
1234 ensure_len(bytes, offset + 2)?;
1235 Ok(u16::from_le_bytes(
1236 bytes[offset..offset + 2].try_into().expect("len checked"),
1237 ))
1238}
1239
1240fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, DatabaseHeaderError> {
1241 ensure_len(bytes, offset + 8)?;
1242 Ok(u64::from_le_bytes(
1243 bytes[offset..offset + 8].try_into().expect("len checked"),
1244 ))
1245}
1246
1247fn write_u32(bytes: &mut [u8], offset: usize, value: u32) -> Result<(), DatabaseHeaderError> {
1248 ensure_len(bytes, offset + 4)?;
1249 bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1250 Ok(())
1251}
1252
1253fn write_u16(bytes: &mut [u8], offset: usize, value: u16) -> Result<(), DatabaseHeaderError> {
1254 ensure_len(bytes, offset + 2)?;
1255 bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
1256 Ok(())
1257}
1258
1259fn write_u64(bytes: &mut [u8], offset: usize, value: u64) -> Result<(), DatabaseHeaderError> {
1260 ensure_len(bytes, offset + 8)?;
1261 bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
1262 Ok(())
1263}
1264
1265#[cfg(test)]
1266mod tests {
1267 use super::*;
1268
1269 fn sealed_zone(gen_a: Option<u64>, gen_b: Option<u64>) -> Vec<u8> {
1270 let mut zone = vec![0u8; PAGED_SUPERBLOCK_ZONE_SIZE];
1271 for (copy_index, generation) in [gen_a, gen_b].into_iter().enumerate() {
1272 let Some(generation) = generation else {
1273 continue;
1274 };
1275 let start = copy_index * PAGED_SUPERBLOCK_SLOT_SIZE;
1276 let slot = &mut zone[start..start + PAGED_SUPERBLOCK_SLOT_SIZE];
1277 init_database_header_page(slot, 3).expect("init header");
1278 seal_paged_superblock_slot(slot, copy_index, generation).expect("seal slot");
1279 }
1280 zone
1281 }
1282
1283 #[test]
1284 fn superblock_zone_fits_two_sector_aligned_slots_before_page_one() {
1285 assert_eq!(PAGED_SUPERBLOCK_SLOT_SIZE, 4096);
1286 assert_eq!(PAGED_SUPERBLOCK_ZONE_SIZE, 8192);
1287 assert!(PAGED_SUPERBLOCK_ZONE_SIZE <= PAGED_PAGE_SIZE);
1290 assert!(DB_HEADER_MIN_LEN < PAGED_SUPERBLOCK_TRAILER_OFFSET);
1292 assert_eq!(paged_superblock_slot_offset(0), 0);
1293 assert_eq!(paged_superblock_slot_offset(1), 4096);
1294 }
1295
1296 #[test]
1297 fn sealed_slot_round_trips_generation_and_keeps_the_header_readable() {
1298 let mut slot = vec![0u8; PAGED_SUPERBLOCK_SLOT_SIZE];
1299 init_database_header_page(&mut slot, 7).expect("init header");
1300 seal_paged_superblock_slot(&mut slot, 1, 42).expect("seal slot");
1301
1302 assert_eq!(paged_superblock_slot_generation(&slot, 1), Some(42));
1303 assert_eq!(
1304 database_header_page_count(&slot).expect("database_header_page_count() should succeed"),
1305 7
1306 );
1307 }
1308
1309 #[test]
1310 fn selection_prefers_the_newest_valid_copy_and_survives_one_torn_slot() {
1311 let zone = sealed_zone(Some(4), Some(5));
1312 assert_eq!(
1313 select_paged_superblock(&zone),
1314 Some(PagedSuperblockSelection {
1315 copy_index: 1,
1316 generation: 5
1317 })
1318 );
1319
1320 let mut torn = zone.clone();
1322 torn[PAGED_SUPERBLOCK_SLOT_SIZE + 64] ^= 0xFF;
1323 assert_eq!(
1324 select_paged_superblock(&torn),
1325 Some(PagedSuperblockSelection {
1326 copy_index: 0,
1327 generation: 4
1328 })
1329 );
1330 }
1331
1332 #[test]
1333 fn a_misdirected_slot_copy_is_rejected_not_read_as_an_older_generation() {
1334 let zone = sealed_zone(Some(9), None);
1336 let (slot_zero, _) = zone.split_at(PAGED_SUPERBLOCK_SLOT_SIZE);
1337 assert_eq!(paged_superblock_slot_generation(slot_zero, 1), None);
1338 }
1339
1340 #[test]
1341 fn both_copies_invalid_yields_no_selection() {
1342 assert_eq!(
1343 select_paged_superblock(&vec![0u8; PAGED_SUPERBLOCK_ZONE_SIZE]),
1344 None
1345 );
1346 assert_eq!(select_paged_superblock(&[]), None);
1347
1348 let mut zone = sealed_zone(Some(1), Some(2));
1349 zone[64] ^= 0xFF;
1350 zone[PAGED_SUPERBLOCK_SLOT_SIZE + 64] ^= 0xFF;
1351 assert_eq!(select_paged_superblock(&zone), None);
1352 }
1353
1354 #[test]
1355 fn the_next_copy_to_write_is_always_the_stale_one() {
1356 assert_eq!(paged_superblock_next_copy(None), 0);
1357 assert_eq!(
1358 paged_superblock_next_copy(Some(PagedSuperblockSelection {
1359 copy_index: 0,
1360 generation: 3
1361 })),
1362 1
1363 );
1364 assert_eq!(
1365 paged_superblock_next_copy(Some(PagedSuperblockSelection {
1366 copy_index: 1,
1367 generation: 3
1368 })),
1369 0
1370 );
1371 }
1372
1373 #[test]
1374 fn bit_rot_anywhere_in_the_slot_payload_fails_the_crc() {
1375 let mut slot = vec![0u8; PAGED_SUPERBLOCK_SLOT_SIZE];
1376 init_database_header_page(&mut slot, 3).expect("init header");
1377 seal_paged_superblock_slot(&mut slot, 0, 1).expect("seal slot");
1378
1379 for offset in [
1380 0,
1381 40,
1382 DB_HEADER_MIN_LEN - 1,
1383 PAGED_SUPERBLOCK_SLOT_SIZE - 33,
1384 ] {
1385 let mut rotted = slot.clone();
1386 rotted[offset] ^= 0x01;
1387 assert_eq!(
1388 paged_superblock_slot_generation(&rotted, 0),
1389 None,
1390 "bit rot at offset {offset} must fail the slot CRC"
1391 );
1392 }
1393 }
1394
1395 #[test]
1396 fn paged_page_header_round_trips_raw_layout() {
1397 let header = PagedPageHeader {
1398 page_type: 13,
1399 flags: 0b1010_0001,
1400 cell_count: 42,
1401 free_start: 44,
1402 free_end: 16_000,
1403 page_id: 99,
1404 parent_id: 88,
1405 right_child: 77,
1406 lsn: 66,
1407 checksum: 55,
1408 };
1409
1410 let encoded = encode_paged_page_header(&header);
1411
1412 assert_eq!(encoded.len(), PAGED_PAGE_HEADER_SIZE);
1413 assert_eq!(decode_paged_page_header(&encoded), header);
1414 }
1415
1416 #[test]
1417 fn paged_page_header_accessors_update_expected_offsets() {
1418 let mut page = [0u8; PAGED_PAGE_SIZE];
1419 page[0] = 13;
1420
1421 set_paged_page_cell_count(&mut page, 2);
1422 set_paged_page_free_start(&mut page, 34);
1423 set_paged_page_free_end(&mut page, 4096);
1424 set_paged_page_parent_id(&mut page, 7);
1425 set_paged_page_right_child(&mut page, 8);
1426 set_paged_page_lsn(&mut page, 9);
1427 set_paged_page_checksum(&mut page, 10);
1428
1429 assert_eq!(paged_page_type(&page), 13);
1430 assert_eq!(paged_page_cell_count(&page), 2);
1431 assert_eq!(paged_page_free_start(&page), 34);
1432 assert_eq!(paged_page_free_end(&page), 4096);
1433 assert_eq!(paged_page_parent_id(&page), 7);
1434 assert_eq!(paged_page_right_child(&page), 8);
1435 assert_eq!(paged_page_lsn(&page), 9);
1436 assert_eq!(paged_page_checksum(&page), 10);
1437 clear_paged_page_checksum(&mut page);
1438 assert_eq!(paged_page_checksum(&page), 0);
1439 }
1440
1441 #[test]
1442 fn paged_cell_helpers_round_trip_pointer_and_payload() {
1443 let mut page = [0u8; PAGED_PAGE_SIZE];
1444 let pointer = (PAGED_PAGE_SIZE - 14) as u16;
1445
1446 assert!(write_paged_cell(&mut page, pointer, b"key", b"value"));
1447 assert!(set_paged_cell_pointer(&mut page, 0, pointer));
1448
1449 assert_eq!(paged_cell_pointer(&page, 0), Some(pointer));
1450 let cell = paged_cell_bytes(&page, pointer).expect("cell bytes");
1451 let (key, value) = paged_cell_key_value(cell).expect("key value");
1452
1453 assert_eq!(key, b"key");
1454 assert_eq!(value, b"value");
1455 assert_eq!(paged_cell_total_len(&page, pointer), Some(14));
1456 }
1457
1458 #[test]
1459 fn database_header_field_helpers_preserve_page_zero_offsets() {
1460 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1461
1462 init_database_header_page(&mut page, 7).expect("init database header page");
1463 set_database_header_version(&mut page, PAGE_FILE_VERSION + 1).expect("set version");
1464 set_database_header_page_count(&mut page, 9).expect("set page count");
1465 set_database_header_freelist_head(&mut page, 4).expect("set freelist head");
1466
1467 assert!(database_header_magic_matches(&page));
1468 assert!(matches!(
1469 decode_database_header(&page),
1470 Err(DatabaseHeaderError::UnsupportedDatabaseVersion { .. })
1471 ));
1472 set_database_header_version(&mut page, PAGE_FILE_VERSION).expect("restore version");
1473 assert_eq!(
1474 database_header_page_size(&page).unwrap(),
1475 PAGED_PAGE_SIZE as u32
1476 );
1477 assert_eq!(database_header_page_count(&page).unwrap(), 9);
1478 assert_eq!(database_header_freelist_head(&page).unwrap(), 4);
1479 }
1480
1481 #[test]
1482 fn required_capabilities_round_trip_and_unknown_bits_fail_before_page_reads() {
1483 let mut page = vec![0u8; PAGED_SUPERBLOCK_SLOT_SIZE];
1484 let header = DatabaseHeader {
1485 version: PAGE_REQUIRED_CAPABILITIES_FILE_VERSION,
1486 required_capabilities: FILE_CAPABILITY_TEMPERATURE,
1487 ..DatabaseHeader::default()
1488 };
1489 encode_database_header(&mut page, &header).expect("encode capabilities");
1490 assert_eq!(
1491 decode_database_header(&page)
1492 .expect("supported capabilities")
1493 .required_capabilities,
1494 FILE_CAPABILITY_TEMPERATURE
1495 );
1496
1497 set_database_header_required_capabilities(&mut page, 1 << 63)
1498 .expect("set unknown capability");
1499 assert_eq!(
1500 decode_database_header(&page),
1501 Err(DatabaseHeaderError::UnsupportedRequiredCapabilities(
1502 1 << 63
1503 ))
1504 );
1505 }
1506
1507 #[test]
1508 fn disk_residency_moves_from_legacy_version_to_explicit_capability() {
1509 let legacy = DatabaseHeader {
1510 version: PAGE_DISK_RESIDENCY_FILE_VERSION,
1511 ..DatabaseHeader::default()
1512 };
1513 assert!(legacy.requires_disk_residency());
1514
1515 let resident_result_cache = DatabaseHeader {
1516 version: PAGE_RESULT_CACHE_FILE_VERSION,
1517 ..DatabaseHeader::default()
1518 };
1519 assert!(!resident_result_cache.requires_disk_residency());
1520
1521 let disk_result_cache = DatabaseHeader {
1522 version: PAGE_RESULT_CACHE_DISK_FILE_VERSION,
1523 ..DatabaseHeader::default()
1524 };
1525 assert!(disk_result_cache.requires_disk_residency());
1526
1527 let temperature_only = DatabaseHeader {
1528 version: PAGE_REQUIRED_CAPABILITIES_FILE_VERSION,
1529 required_capabilities: FILE_CAPABILITY_TEMPERATURE,
1530 ..DatabaseHeader::default()
1531 };
1532 assert!(!temperature_only.requires_disk_residency());
1533
1534 let disk_resident = DatabaseHeader {
1535 required_capabilities: FILE_CAPABILITY_DISK_RESIDENCY,
1536 ..temperature_only
1537 };
1538 assert!(disk_resident.requires_disk_residency());
1539 }
1540
1541 #[test]
1542 fn legacy_headers_ignore_the_reserved_capability_bytes() {
1543 let mut page = vec![0u8; PAGED_SUPERBLOCK_SLOT_SIZE];
1544 init_database_header_page(&mut page, 7).expect("init header");
1545 set_database_header_required_capabilities(&mut page, u64::MAX)
1546 .expect("fill reserved bytes");
1547
1548 assert_eq!(
1549 decode_database_header(&page)
1550 .expect("legacy header")
1551 .required_capabilities,
1552 0
1553 );
1554 }
1555
1556 #[test]
1557 fn paged_dwb_frame_round_trips_entries_and_validates_checksum() {
1558 let mut page = [0u8; PAGED_PAGE_SIZE];
1559 page[0] = 7;
1560 let frame = encode_paged_dwb_frame([(42, &page)]);
1561
1562 let entries = decode_paged_dwb_frame(&frame).expect("decode DWB frame");
1563 assert_eq!(entries.len(), 1);
1564 assert_eq!(entries[0].page_id, 42);
1565 assert_eq!(entries[0].page, page);
1566
1567 let mut corrupted = frame;
1568 let last = corrupted.len() - 1;
1569 corrupted[last] ^= 0xFF;
1570 assert!(matches!(
1571 decode_paged_dwb_frame(&corrupted),
1572 Err(PagedDwbFrameError::ChecksumMismatch { .. })
1573 ));
1574 }
1575
1576 #[test]
1577 fn paged_encryption_header_round_trips_marker_and_raw_bytes() {
1578 let header = PagedEncryptionHeader {
1579 salt: [7u8; PAGED_ENCRYPTION_SALT_SIZE],
1580 key_check: vec![9u8; PAGED_ENCRYPTION_KEY_CHECK_BLOB_SIZE],
1581 };
1582 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1583 let bytes = encode_paged_encryption_header(&header);
1584
1585 write_paged_encryption_marker_and_header(&mut page, &bytes)
1586 .expect("write encryption marker");
1587
1588 assert!(paged_encryption_marker_present(&page));
1589 let raw = paged_encryption_header_bytes(&page).expect("header bytes");
1590 assert_eq!(decode_paged_encryption_header(raw).unwrap(), header);
1591 }
1592
1593 #[test]
1594 fn database_header_round_trips_page_zero_contract() {
1595 let header = DatabaseHeader {
1596 version: PAGE_FILE_VERSION,
1597 page_size: PAGED_PAGE_SIZE as u32,
1598 page_count: 42,
1599 freelist_head: 7,
1600 schema_version: 3,
1601 checkpoint_lsn: 99,
1602 checkpoint_in_progress: true,
1603 checkpoint_target_lsn: 123,
1604 required_capabilities: FILE_CAPABILITY_TEMPERATURE,
1605 physical: PhysicalFileHeader {
1606 format_version: 11,
1607 sequence: 12,
1608 manifest_oldest_root: 13,
1609 manifest_root: 14,
1610 free_set_root: 15,
1611 manifest_page: 16,
1612 manifest_checksum: 17,
1613 collection_roots_page: 18,
1614 collection_roots_checksum: 19,
1615 collection_root_count: 20,
1616 snapshot_count: 21,
1617 index_count: 22,
1618 catalog_collection_count: 23,
1619 catalog_total_entities: 24,
1620 export_count: 25,
1621 graph_projection_count: 26,
1622 analytics_job_count: 27,
1623 manifest_event_count: 28,
1624 registry_page: 29,
1625 registry_checksum: 30,
1626 recovery_page: 31,
1627 recovery_checksum: 32,
1628 catalog_page: 33,
1629 catalog_checksum: 34,
1630 metadata_state_page: 35,
1631 metadata_state_checksum: 36,
1632 vector_artifact_page: 37,
1633 vector_artifact_checksum: 38,
1634 },
1635 };
1636 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1637
1638 encode_database_header(&mut page, &header).expect("encode header");
1639
1640 assert!(database_header_magic_matches(&page));
1641 let decoded = decode_database_header(&page).expect("decode header");
1642 assert_eq!(decoded, header);
1643 }
1644
1645 #[test]
1646 fn database_header_rejects_newer_versions() {
1647 let mut page = vec![0u8; PAGED_PAGE_SIZE];
1648 let header = DatabaseHeader {
1649 version: PAGE_FILE_VERSION + 1,
1650 ..DatabaseHeader::default()
1651 };
1652 encode_database_header(&mut page, &header).expect("encode header");
1653
1654 assert!(matches!(
1655 decode_database_header(&page),
1656 Err(DatabaseHeaderError::UnsupportedDatabaseVersion { .. })
1657 ));
1658 }
1659}