1use std::collections::HashMap;
8use std::fs::{self, File, OpenOptions};
9use std::io::{Read, Seek, SeekFrom, Write};
10use std::path::{Path, PathBuf};
11use std::sync::{Arc, Mutex, OnceLock};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use fs2::FileExt;
15
16pub type RdbFileResult<T> = Result<T, RdbFileError>;
17
18pub const DEFAULT_FORMAT_VERSION: u32 = 1;
19
20#[derive(Debug)]
21pub enum RdbFileError {
22 InvalidOperation(String),
23 Io(std::io::Error),
24 ZoneUnrecoverable {
28 zone: &'static str,
29 path: PathBuf,
30 },
31}
32
33impl std::fmt::Display for RdbFileError {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 Self::InvalidOperation(msg) => write!(f, "INVALID_OPERATION: {msg}"),
37 Self::Io(err) => write!(f, "io error: {err}"),
38 Self::ZoneUnrecoverable { zone, path } => write!(
39 f,
40 "{zone} zone of {} failed validation, so the store will not be opened \
41 (opening it could only return data the zone can no longer vouch for). \
42 Run scrub to classify the fault and red salvage to extract every entity the \
43 damage did not touch; red salvage never writes into the damaged file \
44 (ADR 0074 §2/§4).",
45 path.display()
46 ),
47 }
48 }
49}
50
51impl std::error::Error for RdbFileError {}
52
53impl From<std::io::Error> for RdbFileError {
54 fn from(err: std::io::Error) -> Self {
55 Self::Io(err)
56 }
57}
58
59fn crc32(data: &[u8]) -> u32 {
60 let mut hasher = crc32fast::Hasher::new();
61 hasher.update(data);
62 hasher.finalize()
63}
64
65pub const EMBEDDED_RDB_SUPERBLOCK_SIZE: u64 = 4096;
66pub const EMBEDDED_RDB_SUPERBLOCK_0_OFFSET: u64 = 0;
67pub const EMBEDDED_RDB_SUPERBLOCK_1_OFFSET: u64 = EMBEDDED_RDB_SUPERBLOCK_SIZE;
68
69pub const EMBEDDED_RDB_MANIFEST_SLOT_SIZE: u64 = 4096;
78pub const EMBEDDED_RDB_MANIFEST_0_OFFSET: u64 = EMBEDDED_RDB_SUPERBLOCK_SIZE * 2;
79pub const EMBEDDED_RDB_MANIFEST_1_OFFSET: u64 =
80 EMBEDDED_RDB_MANIFEST_0_OFFSET + EMBEDDED_RDB_MANIFEST_SLOT_SIZE;
81pub const EMBEDDED_RDB_MANIFEST_ZONE_END: u64 =
82 EMBEDDED_RDB_MANIFEST_1_OFFSET + EMBEDDED_RDB_MANIFEST_SLOT_SIZE;
83
84const SUPERBLOCK_MAGIC: &[u8; 8] = b"RDBSBLK1";
85const MANIFEST_MAGIC: &[u8; 8] = b"RDBMNFS1";
86const SUPERBLOCK_VERSION: u32 = 2;
87const MANIFEST_VERSION: u32 = 2;
88const LEGACY_SUPERBLOCK_VERSION: u32 = 1;
89const LEGACY_MANIFEST_VERSION: u32 = 1;
90const CHECKSUM_LEN: usize = 4;
91const MANIFEST_REGION_BYTES: u64 = EMBEDDED_RDB_MANIFEST_SLOT_SIZE;
92const WAL_REGION_BYTES: u64 = 64 * 1024;
93const SNAPSHOT_ALIGNMENT: u64 = 4096;
94const SNAPSHOT_MAGIC: &[u8; 4] = b"RDST";
95const WAL_FRAME_MAGIC: &[u8; 8] = b"RDBEWAL1";
96const WAL_FRAME_VERSION: u16 = 2;
97const WAL_FRAME_HEADER_BYTES: usize = 8 + 2 + 2 + 8 + 4 + 4 + 4 + 4;
98const LEGACY_WAL_FRAME_HEADER_BYTES: usize = 8 + 4 + 4;
99const CRASH_INJECT_ENV: &str = "REDDB_EMBEDDED_RDB_CRASH_AT";
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct EmbeddedRdbManifest {
103 pub version: u32,
104 pub wal_region_offset: u64,
105 pub wal_region_bytes: u64,
106 pub wal_recovery_boundary: u64,
107 pub wal_live_bytes: u64,
108 pub snapshot_offset: u64,
109 pub snapshot_bytes: u64,
110 pub snapshot_checksum: u32,
111 pub created_at_unix_ms: u128,
112 pub checksum: u32,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct EmbeddedRdbSuperblock {
117 pub copy_index: u8,
118 pub generation: u64,
119 pub format_version: u32,
120 pub manifest_offset: u64,
121 pub manifest_len: u64,
122 pub manifest_checksum: u32,
123 pub wal_region_offset: u64,
124 pub wal_region_bytes: u64,
125 pub wal_recovery_boundary: u64,
126 pub wal_live_bytes: u64,
127 pub snapshot_offset: u64,
128 pub snapshot_bytes: u64,
129 pub snapshot_checksum: u32,
130 pub checksum: u32,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct EmbeddedRdbOpen {
135 pub path: PathBuf,
136 pub selected_superblock: EmbeddedRdbSuperblock,
137 pub manifest: EmbeddedRdbManifest,
138}
139
140#[derive(Debug, Default)]
141struct WalScan {
142 payloads: Vec<Vec<u8>>,
143 next_sequence: u64,
144 previous_frame_crc: u32,
145 valid_bytes: u64,
146}
147
148pub struct EmbeddedRdbArtifact;
149
150impl EmbeddedRdbArtifact {
151 pub fn create(path: impl AsRef<Path>) -> RdbFileResult<EmbeddedRdbOpen> {
152 Self::create_with_snapshot(path, &[])
153 }
154
155 pub fn create_with_snapshot(
156 path: impl AsRef<Path>,
157 snapshot: &[u8],
158 ) -> RdbFileResult<EmbeddedRdbOpen> {
159 let path = path.as_ref();
160 if let Some(parent) = path.parent() {
161 if !parent.as_os_str().is_empty() {
162 fs::create_dir_all(parent)?;
163 }
164 }
165
166 let created_at_unix_ms = now_unix_ms();
167 let wal_region_offset = EMBEDDED_RDB_MANIFEST_ZONE_END;
168 let snapshot_offset = wal_region_offset + WAL_REGION_BYTES;
169 let manifest = EmbeddedRdbManifest {
170 version: MANIFEST_VERSION,
171 wal_region_offset,
172 wal_region_bytes: WAL_REGION_BYTES,
173 wal_recovery_boundary: wal_region_offset,
174 wal_live_bytes: 0,
175 snapshot_offset,
176 snapshot_bytes: snapshot.len() as u64,
177 snapshot_checksum: crc32(snapshot),
178 created_at_unix_ms,
179 checksum: 0,
180 };
181 let manifest_bytes = encode_manifest(manifest);
182 let manifest_checksum = trailer_checksum(&manifest_bytes);
183
184 let mut file = OpenOptions::new()
185 .create(true)
186 .truncate(true)
187 .read(true)
188 .write(true)
189 .open(path)?;
190 file.set_len(snapshot_offset + snapshot.len() as u64)?;
191 write_at(&mut file, EMBEDDED_RDB_MANIFEST_0_OFFSET, &manifest_bytes)?;
192 if !snapshot.is_empty() {
193 write_at(&mut file, snapshot_offset, snapshot)?;
194 }
195
196 let base = EmbeddedRdbSuperblock {
197 copy_index: 0,
198 generation: 1,
199 format_version: DEFAULT_FORMAT_VERSION,
200 manifest_offset: EMBEDDED_RDB_MANIFEST_0_OFFSET,
201 manifest_len: manifest_bytes.len() as u64,
202 manifest_checksum,
203 wal_region_offset,
204 wal_region_bytes: WAL_REGION_BYTES,
205 wal_recovery_boundary: wal_region_offset,
206 wal_live_bytes: 0,
207 snapshot_offset,
208 snapshot_bytes: snapshot.len() as u64,
209 snapshot_checksum: crc32(snapshot),
210 checksum: 0,
211 };
212 Self::write_superblock_copy(&mut file, &base)?;
213 Self::write_superblock_copy(
214 &mut file,
215 &EmbeddedRdbSuperblock {
216 copy_index: 1,
217 generation: 2,
218 ..base
219 },
220 )?;
221 file.sync_all()?;
222
223 Self::open(path)
224 }
225
226 pub fn open(path: impl AsRef<Path>) -> RdbFileResult<EmbeddedRdbOpen> {
227 Self::open_inner(path, true)
228 }
229
230 fn open_for_wal_append(path: impl AsRef<Path>) -> RdbFileResult<EmbeddedRdbOpen> {
231 Self::open_inner(path, false)
232 }
233
234 fn open_inner(
235 path: impl AsRef<Path>,
236 validate_snapshot_refs: bool,
237 ) -> RdbFileResult<EmbeddedRdbOpen> {
238 let path = path.as_ref();
239 let mut file = File::open(path)?;
240 let mut superblocks: Vec<EmbeddedRdbSuperblock> = [
241 read_superblock_copy(&mut file, 0),
242 read_superblock_copy(&mut file, 1),
243 ]
244 .into_iter()
245 .flatten()
246 .collect();
247 superblocks.sort_by_key(|superblock| std::cmp::Reverse(superblock.generation));
248
249 if superblocks.is_empty() {
250 return Err(RdbFileError::ZoneUnrecoverable {
251 zone: "superblock",
252 path: path.to_path_buf(),
253 });
254 }
255
256 for selected_superblock in superblocks {
257 let mut manifest = read_manifest(&mut file, selected_superblock).map_err(|_| {
263 RdbFileError::ZoneUnrecoverable {
264 zone: "manifest",
265 path: path.to_path_buf(),
266 }
267 })?;
268 manifest.wal_recovery_boundary = selected_superblock.wal_recovery_boundary;
269 manifest.wal_live_bytes = selected_superblock.wal_live_bytes;
270 if validate_snapshot_refs && !snapshot_reference_valid(&mut file, &manifest)? {
271 continue;
272 }
273 return Ok(EmbeddedRdbOpen {
274 path: path.to_path_buf(),
275 selected_superblock,
276 manifest,
277 });
278 }
279
280 Err(RdbFileError::ZoneUnrecoverable {
281 zone: "snapshot",
282 path: path.to_path_buf(),
283 })
284 }
285
286 pub fn wal_payloads_encoded_len(payloads: &[Vec<u8>]) -> RdbFileResult<u64> {
287 let mut len = 0u64;
288 for payload in payloads {
289 let payload_len = u32::try_from(payload.len()).map_err(|_| {
290 RdbFileError::InvalidOperation("embedded wal payload too large".into())
291 })?;
292 let frame_len = WAL_FRAME_HEADER_BYTES as u64 + payload_len as u64;
293 len = len.checked_add(frame_len).ok_or_else(|| {
294 RdbFileError::InvalidOperation("embedded wal encoded length overflow".into())
295 })?;
296 }
297 Ok(len)
298 }
299
300 pub fn write_snapshot_with_wal_capacity(
301 path: impl AsRef<Path>,
302 snapshot: &[u8],
303 min_wal_bytes: u64,
304 ) -> RdbFileResult<EmbeddedRdbOpen> {
305 let path = path.as_ref();
306 let path_lock = embedded_path_lock(path);
307 let _path_guard = path_lock
308 .lock()
309 .unwrap_or_else(|poisoned| poisoned.into_inner());
310 let lock_file = OpenOptions::new().read(true).write(true).open(path)?;
311 lock_file.lock_exclusive()?;
312
313 let open = Self::open(path)?;
314 let wal_scan = scan_wal(&open)?;
315 let checkpoint_boundary = wal_boundary_after_live_bytes(&open, wal_scan.valid_bytes)?;
316 let wal_region_bytes =
317 grow_wal_region_bytes(open.manifest.wal_region_bytes, min_wal_bytes)?;
318 let snapshot_offset = next_snapshot_offset(path, &open, wal_region_bytes, snapshot)?;
319 let snapshot_checksum = crc32(snapshot);
320 let manifest = EmbeddedRdbManifest {
321 wal_region_bytes,
322 wal_recovery_boundary: checkpoint_boundary,
323 wal_live_bytes: 0,
324 snapshot_offset,
325 snapshot_bytes: snapshot.len() as u64,
326 snapshot_checksum,
327 checksum: 0,
328 ..open.manifest
329 };
330 let manifest_bytes = encode_manifest(manifest);
331 let manifest_checksum = trailer_checksum(&manifest_bytes);
332
333 let mut file = OpenOptions::new().read(true).write(true).open(path)?;
334 file.set_len(snapshot_offset + snapshot.len() as u64)?;
335 if !snapshot.is_empty() {
336 write_at(&mut file, snapshot_offset, snapshot)?;
337 }
338 crash_inject("snapshot_after_image_write");
339 file.sync_data()?;
340 crash_inject("snapshot_after_image_sync");
341
342 let next_manifest_offset = inactive_manifest_offset(open.selected_superblock)?;
347 write_at(&mut file, next_manifest_offset, &manifest_bytes)?;
348 crash_inject("snapshot_after_manifest_write");
349 file.sync_data()?;
350 crash_inject("snapshot_after_manifest_sync");
351
352 let next_copy_index = if open.selected_superblock.copy_index == 0 {
353 1
354 } else {
355 0
356 };
357 let next_superblock = EmbeddedRdbSuperblock {
358 copy_index: next_copy_index,
359 generation: open.selected_superblock.generation.saturating_add(1),
360 manifest_offset: next_manifest_offset,
361 manifest_len: manifest_bytes.len() as u64,
362 manifest_checksum,
363 wal_region_bytes,
364 wal_recovery_boundary: checkpoint_boundary,
365 wal_live_bytes: 0,
366 snapshot_offset,
367 snapshot_bytes: snapshot.len() as u64,
368 snapshot_checksum,
369 checksum: 0,
370 ..open.selected_superblock
371 };
372 Self::write_superblock_copy(&mut file, &next_superblock)?;
373 crash_inject("snapshot_after_superblock_write");
374 file.sync_all()?;
375 lock_file.unlock()?;
376 Self::open(path)
377 }
378
379 pub fn open_strict_manifest(path: impl AsRef<Path>) -> RdbFileResult<EmbeddedRdbOpen> {
380 let path = path.as_ref();
381 let mut file = File::open(path)?;
382 let selected_superblock = [
383 read_superblock_copy(&mut file, 0),
384 read_superblock_copy(&mut file, 1),
385 ]
386 .into_iter()
387 .flatten()
388 .max_by_key(|superblock| superblock.generation)
389 .ok_or_else(|| RdbFileError::InvalidOperation("no valid embedded superblock".into()))?;
390
391 let mut manifest = read_manifest(&mut file, selected_superblock)?;
392 manifest.wal_recovery_boundary = selected_superblock.wal_recovery_boundary;
393 manifest.wal_live_bytes = selected_superblock.wal_live_bytes;
397 Ok(EmbeddedRdbOpen {
398 path: path.to_path_buf(),
399 selected_superblock,
400 manifest,
401 })
402 }
403
404 pub fn read_snapshot(open: &EmbeddedRdbOpen) -> RdbFileResult<Option<Vec<u8>>> {
405 if open.manifest.snapshot_bytes == 0 {
406 return Ok(None);
407 }
408 let mut file = File::open(&open.path)?;
409 let mut bytes = vec![0u8; open.manifest.snapshot_bytes as usize];
410 file.seek(SeekFrom::Start(open.manifest.snapshot_offset))?;
411 file.read_exact(&mut bytes)?;
412 let checksum = crc32(&bytes);
413 if checksum != open.manifest.snapshot_checksum {
414 return Err(RdbFileError::InvalidOperation(format!(
415 "embedded snapshot checksum mismatch: stored {:#010x}, computed {:#010x}",
416 open.manifest.snapshot_checksum, checksum
417 )));
418 }
419 if bytes.len() >= SNAPSHOT_MAGIC.len() && &bytes[..SNAPSHOT_MAGIC.len()] != SNAPSHOT_MAGIC {
420 return Err(RdbFileError::InvalidOperation(
421 "invalid embedded snapshot magic".into(),
422 ));
423 }
424 Ok(Some(bytes))
425 }
426
427 pub fn write_snapshot(
428 path: impl AsRef<Path>,
429 snapshot: &[u8],
430 ) -> RdbFileResult<EmbeddedRdbOpen> {
431 Self::write_snapshot_with_wal_capacity(path, snapshot, 0)
432 }
433
434 pub fn read_wal_payloads(open: &EmbeddedRdbOpen) -> RdbFileResult<Vec<Vec<u8>>> {
435 Ok(scan_wal(open)?.payloads)
436 }
437
438 pub fn append_wal_payloads(
439 path: impl AsRef<Path>,
440 payloads: &[Vec<u8>],
441 ) -> RdbFileResult<EmbeddedRdbOpen> {
442 let path = path.as_ref();
443 if payloads.is_empty() {
444 return Self::open(path);
445 }
446
447 let path_lock = embedded_path_lock(path);
448 let _path_guard = path_lock
449 .lock()
450 .unwrap_or_else(|poisoned| poisoned.into_inner());
451 let lock_file = OpenOptions::new().read(true).write(true).open(path)?;
452 lock_file.lock_exclusive()?;
453
454 let open = Self::open_for_wal_append(path)?;
455 let wal_scan = scan_wal(&open)?;
456 let mut sequence = wal_scan.next_sequence;
457 let mut previous_frame_crc = wal_scan.previous_frame_crc;
458 let mut encoded = Vec::new();
459 for payload in payloads {
460 let (frame, frame_crc) = encode_wal_frame(sequence, previous_frame_crc, payload)?;
461 encoded.extend_from_slice(&frame);
462 previous_frame_crc = frame_crc;
463 sequence = sequence.saturating_add(1);
464 }
465
466 let encoded_len = encoded.len() as u64;
467 let free_bytes = open
468 .manifest
469 .wal_region_bytes
470 .checked_sub(wal_scan.valid_bytes)
471 .ok_or_else(|| {
472 RdbFileError::InvalidOperation("embedded wal live bytes overflow".into())
473 })?;
474 if encoded_len > free_bytes {
475 return Err(RdbFileError::InvalidOperation(
476 "embedded wal region full".into(),
477 ));
478 }
479 let next_boundary =
480 wal_boundary_after_live_bytes(&open, wal_scan.valid_bytes + encoded_len)?;
481
482 let mut file = OpenOptions::new().read(true).write(true).open(path)?;
483 write_circular_wal_bytes(&mut file, &open, &encoded)?;
484 crash_inject("wal_after_frame_write");
485 file.sync_data()?;
486 crash_inject("wal_after_frame_sync");
487
488 let next_copy_index = if open.selected_superblock.copy_index == 0 {
489 1
490 } else {
491 0
492 };
493 let next_superblock = EmbeddedRdbSuperblock {
494 copy_index: next_copy_index,
495 generation: open.selected_superblock.generation.saturating_add(1),
496 wal_recovery_boundary: next_boundary,
497 wal_live_bytes: wal_scan.valid_bytes + encoded_len,
498 checksum: 0,
499 ..open.selected_superblock
500 };
501 Self::write_superblock_copy(&mut file, &next_superblock)?;
502 crash_inject("wal_after_superblock_write");
503 file.sync_all()?;
504 lock_file.unlock()?;
505 Self::open(path)
506 }
507
508 pub fn write_superblock_copy(
509 file: &mut File,
510 superblock: &EmbeddedRdbSuperblock,
511 ) -> RdbFileResult<()> {
512 let offset = superblock_offset(superblock.copy_index)?;
513 write_at(file, offset, &encode_superblock(*superblock)?)?;
514 Ok(())
515 }
516}
517
518fn read_superblock_copy(file: &mut File, copy_index: u8) -> Option<EmbeddedRdbSuperblock> {
519 let offset = superblock_offset(copy_index).ok()?;
520 let mut bytes = vec![0u8; EMBEDDED_RDB_SUPERBLOCK_SIZE as usize];
521 file.seek(SeekFrom::Start(offset)).ok()?;
522 file.read_exact(&mut bytes).ok()?;
523 decode_superblock(copy_index, &bytes).ok()
524}
525
526fn inactive_manifest_offset(superblock: EmbeddedRdbSuperblock) -> RdbFileResult<u64> {
533 match superblock.manifest_offset {
534 EMBEDDED_RDB_MANIFEST_0_OFFSET => Ok(EMBEDDED_RDB_MANIFEST_1_OFFSET),
535 EMBEDDED_RDB_MANIFEST_1_OFFSET => Ok(EMBEDDED_RDB_MANIFEST_0_OFFSET),
536 other => Err(RdbFileError::InvalidOperation(format!(
537 "superblock names a manifest offset outside the zone: {other}"
538 ))),
539 }
540}
541
542fn read_manifest(
543 file: &mut File,
544 superblock: EmbeddedRdbSuperblock,
545) -> RdbFileResult<EmbeddedRdbManifest> {
546 if superblock.manifest_len < CHECKSUM_LEN as u64
547 || superblock.manifest_len > MANIFEST_REGION_BYTES
548 {
549 return Err(RdbFileError::InvalidOperation(format!(
550 "invalid embedded manifest length {}",
551 superblock.manifest_len
552 )));
553 }
554 if !matches!(
557 superblock.manifest_offset,
558 EMBEDDED_RDB_MANIFEST_0_OFFSET | EMBEDDED_RDB_MANIFEST_1_OFFSET
559 ) {
560 return Err(RdbFileError::InvalidOperation(format!(
561 "superblock names a manifest offset outside the zone: {}",
562 superblock.manifest_offset
563 )));
564 }
565
566 let mut bytes = vec![0u8; superblock.manifest_len as usize];
567 file.seek(SeekFrom::Start(superblock.manifest_offset))?;
568 file.read_exact(&mut bytes)?;
569 let checksum = trailer_checksum(&bytes);
570 if checksum != superblock.manifest_checksum {
571 return Err(RdbFileError::InvalidOperation(format!(
572 "embedded manifest checksum mismatch: stored {:#010x}, computed {:#010x}",
573 superblock.manifest_checksum, checksum
574 )));
575 }
576 decode_manifest(&bytes)
577}
578
579fn snapshot_reference_valid(
580 file: &mut File,
581 manifest: &EmbeddedRdbManifest,
582) -> RdbFileResult<bool> {
583 if manifest.snapshot_bytes == 0 {
584 return Ok(true);
585 }
586 let snapshot_end = manifest
587 .snapshot_offset
588 .checked_add(manifest.snapshot_bytes)
589 .ok_or_else(|| RdbFileError::InvalidOperation("embedded snapshot end overflow".into()))?;
590 if snapshot_end > file.metadata()?.len() {
591 return Ok(false);
592 }
593
594 let mut bytes = vec![0u8; manifest.snapshot_bytes as usize];
595 file.seek(SeekFrom::Start(manifest.snapshot_offset))?;
596 if file.read_exact(&mut bytes).is_err() {
597 return Ok(false);
598 }
599 if crc32(&bytes) != manifest.snapshot_checksum {
600 return Ok(false);
601 }
602 if bytes.len() >= SNAPSHOT_MAGIC.len() && &bytes[..SNAPSHOT_MAGIC.len()] != SNAPSHOT_MAGIC {
603 return Ok(false);
604 }
605 Ok(true)
606}
607
608fn grow_wal_region_bytes(current: u64, min_required: u64) -> RdbFileResult<u64> {
609 let mut next = current.max(WAL_REGION_BYTES);
610 while next < min_required {
611 next = next.checked_mul(2).ok_or_else(|| {
612 RdbFileError::InvalidOperation("embedded wal region size overflow".into())
613 })?;
614 }
615 Ok(next)
616}
617
618fn embedded_path_lock(path: &Path) -> Arc<Mutex<()>> {
619 static LOCKS: OnceLock<Mutex<HashMap<PathBuf, Arc<Mutex<()>>>>> = OnceLock::new();
620 let key = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
621 let mut locks = LOCKS
622 .get_or_init(|| Mutex::new(HashMap::new()))
623 .lock()
624 .unwrap_or_else(|poisoned| poisoned.into_inner());
625 locks
626 .entry(key)
627 .or_insert_with(|| Arc::new(Mutex::new(())))
628 .clone()
629}
630
631fn next_snapshot_offset(
632 path: &Path,
633 open: &EmbeddedRdbOpen,
634 wal_region_bytes: u64,
635 snapshot: &[u8],
636) -> RdbFileResult<u64> {
637 let base = open
638 .manifest
639 .wal_region_offset
640 .checked_add(wal_region_bytes)
641 .ok_or_else(|| {
642 RdbFileError::InvalidOperation("embedded snapshot offset overflow".into())
643 })?;
644 if open.manifest.snapshot_bytes == 0 && snapshot.is_empty() {
645 return Ok(base);
646 }
647
648 let file_len = std::fs::metadata(path).map(|metadata| metadata.len())?;
649 let active_snapshot_end = open
650 .manifest
651 .snapshot_offset
652 .checked_add(open.manifest.snapshot_bytes)
653 .ok_or_else(|| RdbFileError::InvalidOperation("embedded snapshot end overflow".into()))?;
654 align_up(
655 file_len.max(active_snapshot_end).max(base),
656 SNAPSHOT_ALIGNMENT,
657 )
658}
659
660fn align_up(value: u64, alignment: u64) -> RdbFileResult<u64> {
661 if alignment == 0 {
662 return Ok(value);
663 }
664 let remainder = value % alignment;
665 if remainder == 0 {
666 return Ok(value);
667 }
668 value
669 .checked_add(alignment - remainder)
670 .ok_or_else(|| RdbFileError::InvalidOperation("embedded alignment overflow".into()))
671}
672
673fn scan_wal(open: &EmbeddedRdbOpen) -> RdbFileResult<WalScan> {
674 let wal_start = open.manifest.wal_region_offset;
675 let wal_end = wal_start
676 .checked_add(open.manifest.wal_region_bytes)
677 .ok_or_else(|| RdbFileError::InvalidOperation("embedded wal end overflow".into()))?;
678 let append_boundary = open.manifest.wal_recovery_boundary;
679 if append_boundary < wal_start || append_boundary > wal_end {
680 return Err(RdbFileError::InvalidOperation(format!(
681 "invalid embedded wal boundary {append_boundary}"
682 )));
683 }
684 if open.manifest.wal_live_bytes > open.manifest.wal_region_bytes {
685 return Err(RdbFileError::InvalidOperation(format!(
686 "invalid embedded wal live bytes {}",
687 open.manifest.wal_live_bytes
688 )));
689 }
690 if open.manifest.wal_live_bytes == 0 {
691 return Ok(WalScan {
692 next_sequence: 1,
693 ..WalScan::default()
694 });
695 }
696
697 let mut file = File::open(&open.path)?;
698 let file_len = file.metadata()?.len();
699 if file_len <= wal_start {
700 return Ok(WalScan {
701 next_sequence: 1,
702 ..WalScan::default()
703 });
704 }
705 let bytes = read_circular_wal_bytes(&mut file, open, file_len)?;
706 Ok(scan_wal_bytes(&bytes))
707}
708
709fn read_circular_wal_bytes(
710 file: &mut File,
711 open: &EmbeddedRdbOpen,
712 file_len: u64,
713) -> RdbFileResult<Vec<u8>> {
714 let live_start = wal_live_start_relative(open)?;
715 let region_bytes = open.manifest.wal_region_bytes;
716 let live_bytes = open.manifest.wal_live_bytes;
717 let mut remaining = live_bytes.min(file_len.saturating_sub(open.manifest.wal_region_offset));
718 let mut relative = live_start;
719 let mut bytes = Vec::with_capacity(remaining as usize);
720 while remaining > 0 {
721 let contiguous = (region_bytes - relative).min(remaining);
722 let offset = open.manifest.wal_region_offset + relative;
723 file.seek(SeekFrom::Start(offset))?;
724 let mut chunk = vec![0u8; contiguous as usize];
725 file.read_exact(&mut chunk)?;
726 bytes.extend_from_slice(&chunk);
727 remaining -= contiguous;
728 relative = 0;
729 }
730 Ok(bytes)
731}
732
733fn write_circular_wal_bytes(
734 file: &mut File,
735 open: &EmbeddedRdbOpen,
736 bytes: &[u8],
737) -> RdbFileResult<()> {
738 let mut written = 0usize;
739 let mut relative = wal_append_relative(open)?;
740 while written < bytes.len() {
741 if relative == open.manifest.wal_region_bytes {
742 relative = 0;
743 }
744 let contiguous = (open.manifest.wal_region_bytes - relative) as usize;
745 let chunk_len = contiguous.min(bytes.len() - written);
746 write_at(
747 file,
748 open.manifest.wal_region_offset + relative,
749 &bytes[written..written + chunk_len],
750 )?;
751 written += chunk_len;
752 relative += chunk_len as u64;
753 }
754 Ok(())
755}
756
757fn wal_append_relative(open: &EmbeddedRdbOpen) -> RdbFileResult<u64> {
758 open.manifest
759 .wal_recovery_boundary
760 .checked_sub(open.manifest.wal_region_offset)
761 .ok_or_else(|| RdbFileError::InvalidOperation("embedded wal boundary underflow".into()))
762}
763
764fn wal_live_start_relative(open: &EmbeddedRdbOpen) -> RdbFileResult<u64> {
765 let append = wal_append_relative(open)?;
766 let region = open.manifest.wal_region_bytes;
767 if region == 0 {
768 return Err(RdbFileError::InvalidOperation(
769 "embedded wal region is empty".into(),
770 ));
771 }
772 Ok((append + region - (open.manifest.wal_live_bytes % region)) % region)
773}
774
775fn wal_boundary_after_live_bytes(open: &EmbeddedRdbOpen, live_bytes: u64) -> RdbFileResult<u64> {
776 if live_bytes > open.manifest.wal_region_bytes {
777 return Err(RdbFileError::InvalidOperation(format!(
778 "embedded wal live bytes {live_bytes} exceed region size {}",
779 open.manifest.wal_region_bytes
780 )));
781 }
782 let start = if open.manifest.wal_live_bytes == 0 {
783 wal_append_relative(open)?
784 } else {
785 wal_live_start_relative(open)?
786 };
787 let relative = (start + live_bytes) % open.manifest.wal_region_bytes;
788 open.manifest
789 .wal_region_offset
790 .checked_add(relative)
791 .ok_or_else(|| RdbFileError::InvalidOperation("embedded wal boundary overflow".into()))
792}
793
794fn scan_wal_bytes(bytes: &[u8]) -> WalScan {
795 let mut scan = WalScan {
796 next_sequence: 1,
797 ..WalScan::default()
798 };
799 let mut cursor = 0usize;
800 while cursor < bytes.len() {
801 let Some(frame) = decode_next_wal_frame(bytes, cursor, &scan) else {
802 break;
803 };
804 scan.payloads.push(frame.payload);
805 scan.next_sequence = scan.next_sequence.saturating_add(1);
806 scan.previous_frame_crc = frame.frame_crc;
807 cursor = frame.end;
808 scan.valid_bytes = cursor as u64;
809 }
810 scan
811}
812
813struct DecodedWalFrame {
814 payload: Vec<u8>,
815 frame_crc: u32,
816 end: usize,
817}
818
819fn decode_next_wal_frame(bytes: &[u8], start: usize, scan: &WalScan) -> Option<DecodedWalFrame> {
820 let remaining = bytes.len().checked_sub(start)?;
821 if remaining < WAL_FRAME_MAGIC.len() {
822 return None;
823 }
824 if &bytes[start..start + WAL_FRAME_MAGIC.len()] != WAL_FRAME_MAGIC {
825 return None;
826 }
827 if remaining < WAL_FRAME_MAGIC.len() + 2 {
828 return None;
829 }
830 let version_offset = start + WAL_FRAME_MAGIC.len();
831 let version = u16::from_le_bytes(bytes[version_offset..version_offset + 2].try_into().ok()?);
832 if version == WAL_FRAME_VERSION {
833 decode_v2_wal_frame(bytes, start, scan)
834 } else {
835 decode_legacy_wal_frame(bytes, start)
836 }
837}
838
839fn decode_v2_wal_frame(bytes: &[u8], start: usize, scan: &WalScan) -> Option<DecodedWalFrame> {
840 if bytes.len().checked_sub(start)? < WAL_FRAME_HEADER_BYTES {
841 return None;
842 }
843 let header_len_offset = start + 10;
844 let header_len = u16::from_le_bytes(
845 bytes[header_len_offset..header_len_offset + 2]
846 .try_into()
847 .ok()?,
848 ) as usize;
849 if header_len != WAL_FRAME_HEADER_BYTES {
850 return None;
851 }
852 let sequence_offset = start + 12;
853 let sequence = u64::from_le_bytes(
854 bytes[sequence_offset..sequence_offset + 8]
855 .try_into()
856 .ok()?,
857 );
858 if sequence != scan.next_sequence {
859 return None;
860 }
861 let payload_len_offset = start + 20;
862 let payload_len = u32::from_le_bytes(
863 bytes[payload_len_offset..payload_len_offset + 4]
864 .try_into()
865 .ok()?,
866 ) as usize;
867 let payload_crc_offset = start + 24;
868 let payload_crc = u32::from_le_bytes(
869 bytes[payload_crc_offset..payload_crc_offset + 4]
870 .try_into()
871 .ok()?,
872 );
873 let previous_frame_crc_offset = start + 28;
874 let previous_frame_crc = u32::from_le_bytes(
875 bytes[previous_frame_crc_offset..previous_frame_crc_offset + 4]
876 .try_into()
877 .ok()?,
878 );
879 if previous_frame_crc != scan.previous_frame_crc {
880 return None;
881 }
882 let header_crc_offset = start + 32;
883 let header_crc = u32::from_le_bytes(
884 bytes[header_crc_offset..header_crc_offset + 4]
885 .try_into()
886 .ok()?,
887 );
888 if header_crc != crc32(&bytes[start..header_crc_offset]) {
889 return None;
890 }
891 let payload_start = start.checked_add(header_len)?;
892 let end = payload_start.checked_add(payload_len)?;
893 if end > bytes.len() {
894 return None;
895 }
896 let payload = bytes[payload_start..end].to_vec();
897 if crc32(&payload) != payload_crc {
898 return None;
899 }
900 Some(DecodedWalFrame {
901 payload,
902 frame_crc: crc32(&bytes[start..end]),
903 end,
904 })
905}
906
907fn decode_legacy_wal_frame(bytes: &[u8], start: usize) -> Option<DecodedWalFrame> {
908 if bytes.len().checked_sub(start)? < LEGACY_WAL_FRAME_HEADER_BYTES {
909 return None;
910 }
911 let payload_len_offset = start + WAL_FRAME_MAGIC.len();
912 let payload_len = u32::from_le_bytes(
913 bytes[payload_len_offset..payload_len_offset + 4]
914 .try_into()
915 .ok()?,
916 ) as usize;
917 let payload_crc_offset = payload_len_offset + 4;
918 let payload_crc = u32::from_le_bytes(
919 bytes[payload_crc_offset..payload_crc_offset + 4]
920 .try_into()
921 .ok()?,
922 );
923 let payload_start = start.checked_add(LEGACY_WAL_FRAME_HEADER_BYTES)?;
924 let end = payload_start.checked_add(payload_len)?;
925 if end > bytes.len() {
926 return None;
927 }
928 let payload = bytes[payload_start..end].to_vec();
929 if crc32(&payload) != payload_crc {
930 return None;
931 }
932 Some(DecodedWalFrame {
933 payload,
934 frame_crc: crc32(&bytes[start..end]),
935 end,
936 })
937}
938
939fn encode_wal_frame(
940 sequence: u64,
941 previous_frame_crc: u32,
942 payload: &[u8],
943) -> RdbFileResult<(Vec<u8>, u32)> {
944 let payload_len = u32::try_from(payload.len())
945 .map_err(|_| RdbFileError::InvalidOperation("embedded wal payload too large".into()))?;
946 let mut frame = Vec::with_capacity(WAL_FRAME_HEADER_BYTES + payload.len());
947 frame.extend_from_slice(WAL_FRAME_MAGIC);
948 frame.extend_from_slice(&WAL_FRAME_VERSION.to_le_bytes());
949 frame.extend_from_slice(&(WAL_FRAME_HEADER_BYTES as u16).to_le_bytes());
950 frame.extend_from_slice(&sequence.to_le_bytes());
951 frame.extend_from_slice(&payload_len.to_le_bytes());
952 frame.extend_from_slice(&crc32(payload).to_le_bytes());
953 frame.extend_from_slice(&previous_frame_crc.to_le_bytes());
954 let header_crc = crc32(&frame);
955 frame.extend_from_slice(&header_crc.to_le_bytes());
956 frame.extend_from_slice(payload);
957 let frame_crc = crc32(&frame);
958 Ok((frame, frame_crc))
959}
960
961fn encode_superblock(superblock: EmbeddedRdbSuperblock) -> RdbFileResult<Vec<u8>> {
962 let mut bytes = vec![0u8; EMBEDDED_RDB_SUPERBLOCK_SIZE as usize];
963 let mut cursor = 0usize;
964 put_bytes(&mut bytes, &mut cursor, SUPERBLOCK_MAGIC);
965 put_u32(&mut bytes, &mut cursor, SUPERBLOCK_VERSION);
966 put_u8(&mut bytes, &mut cursor, superblock.copy_index);
967 put_u64(&mut bytes, &mut cursor, superblock.generation);
968 put_u32(&mut bytes, &mut cursor, superblock.format_version);
969 put_u64(&mut bytes, &mut cursor, superblock.manifest_offset);
970 put_u64(&mut bytes, &mut cursor, superblock.manifest_len);
971 put_u32(&mut bytes, &mut cursor, superblock.manifest_checksum);
972 put_u64(&mut bytes, &mut cursor, superblock.wal_region_offset);
973 put_u64(&mut bytes, &mut cursor, superblock.wal_region_bytes);
974 put_u64(&mut bytes, &mut cursor, superblock.wal_recovery_boundary);
975 put_u64(&mut bytes, &mut cursor, superblock.wal_live_bytes);
976 put_u64(&mut bytes, &mut cursor, superblock.snapshot_offset);
977 put_u64(&mut bytes, &mut cursor, superblock.snapshot_bytes);
978 put_u32(&mut bytes, &mut cursor, superblock.snapshot_checksum);
979
980 let checksum_offset = bytes.len() - CHECKSUM_LEN;
981 let checksum = crc32(&bytes[..checksum_offset]);
982 bytes[checksum_offset..].copy_from_slice(&checksum.to_le_bytes());
983 Ok(bytes)
984}
985
986fn decode_superblock(copy_index: u8, bytes: &[u8]) -> RdbFileResult<EmbeddedRdbSuperblock> {
987 if bytes.len() != EMBEDDED_RDB_SUPERBLOCK_SIZE as usize {
988 return Err(RdbFileError::InvalidOperation(
989 "invalid embedded superblock size".into(),
990 ));
991 }
992 let checksum_offset = bytes.len() - CHECKSUM_LEN;
993 let stored_checksum = u32::from_le_bytes(bytes[checksum_offset..].try_into().unwrap());
994 let computed_checksum = crc32(&bytes[..checksum_offset]);
995 if stored_checksum != computed_checksum {
996 return Err(RdbFileError::InvalidOperation(
997 "embedded superblock checksum mismatch".into(),
998 ));
999 }
1000
1001 let mut cursor = 0usize;
1002 if take_bytes(bytes, &mut cursor, SUPERBLOCK_MAGIC.len())? != SUPERBLOCK_MAGIC {
1003 return Err(RdbFileError::InvalidOperation(
1004 "invalid embedded superblock magic".into(),
1005 ));
1006 }
1007 let version = take_u32(bytes, &mut cursor)?;
1008 if version != SUPERBLOCK_VERSION && version != LEGACY_SUPERBLOCK_VERSION {
1009 return Err(RdbFileError::InvalidOperation(format!(
1010 "unsupported embedded superblock version {version}"
1011 )));
1012 }
1013 let stored_copy_index = take_u8(bytes, &mut cursor)?;
1014 if stored_copy_index != copy_index {
1015 return Err(RdbFileError::InvalidOperation(
1016 "embedded superblock copy index mismatch".into(),
1017 ));
1018 }
1019
1020 let generation = take_u64(bytes, &mut cursor)?;
1021 let format_version = take_u32(bytes, &mut cursor)?;
1022 let manifest_offset = take_u64(bytes, &mut cursor)?;
1023 let manifest_len = take_u64(bytes, &mut cursor)?;
1024 let manifest_checksum = take_u32(bytes, &mut cursor)?;
1025 let wal_region_offset = take_u64(bytes, &mut cursor)?;
1026 let wal_region_bytes = take_u64(bytes, &mut cursor)?;
1027 let wal_recovery_boundary = take_u64(bytes, &mut cursor)?;
1028 let wal_live_bytes = if version == SUPERBLOCK_VERSION {
1029 take_u64(bytes, &mut cursor)?
1030 } else {
1031 wal_recovery_boundary.saturating_sub(wal_region_offset)
1032 };
1033
1034 Ok(EmbeddedRdbSuperblock {
1035 copy_index: stored_copy_index,
1036 generation,
1037 format_version,
1038 manifest_offset,
1039 manifest_len,
1040 manifest_checksum,
1041 wal_region_offset,
1042 wal_region_bytes,
1043 wal_recovery_boundary,
1044 wal_live_bytes,
1045 snapshot_offset: take_u64(bytes, &mut cursor)?,
1046 snapshot_bytes: take_u64(bytes, &mut cursor)?,
1047 snapshot_checksum: take_u32(bytes, &mut cursor)?,
1048 checksum: stored_checksum,
1049 })
1050}
1051
1052fn encode_manifest(manifest: EmbeddedRdbManifest) -> Vec<u8> {
1053 let mut bytes = vec![0u8; 8 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 16 + CHECKSUM_LEN];
1054 let mut cursor = 0usize;
1055 put_bytes(&mut bytes, &mut cursor, MANIFEST_MAGIC);
1056 put_u32(&mut bytes, &mut cursor, manifest.version);
1057 put_u64(&mut bytes, &mut cursor, manifest.wal_region_offset);
1058 put_u64(&mut bytes, &mut cursor, manifest.wal_region_bytes);
1059 put_u64(&mut bytes, &mut cursor, manifest.wal_recovery_boundary);
1060 put_u64(&mut bytes, &mut cursor, manifest.wal_live_bytes);
1061 put_u64(&mut bytes, &mut cursor, manifest.snapshot_offset);
1062 put_u64(&mut bytes, &mut cursor, manifest.snapshot_bytes);
1063 put_u32(&mut bytes, &mut cursor, manifest.snapshot_checksum);
1064 put_u128(&mut bytes, &mut cursor, manifest.created_at_unix_ms);
1065
1066 let checksum_offset = bytes.len() - CHECKSUM_LEN;
1067 let checksum = crc32(&bytes[..checksum_offset]);
1068 bytes[checksum_offset..].copy_from_slice(&checksum.to_le_bytes());
1069 bytes
1070}
1071
1072fn decode_manifest(bytes: &[u8]) -> RdbFileResult<EmbeddedRdbManifest> {
1073 let checksum_offset = bytes
1074 .len()
1075 .checked_sub(CHECKSUM_LEN)
1076 .ok_or_else(|| RdbFileError::InvalidOperation("embedded manifest too short".into()))?;
1077 let stored_checksum = u32::from_le_bytes(bytes[checksum_offset..].try_into().unwrap());
1078 let computed_checksum = crc32(&bytes[..checksum_offset]);
1079 if stored_checksum != computed_checksum {
1080 return Err(RdbFileError::InvalidOperation(
1081 "embedded manifest checksum mismatch".into(),
1082 ));
1083 }
1084
1085 let mut cursor = 0usize;
1086 if take_bytes(bytes, &mut cursor, MANIFEST_MAGIC.len())? != MANIFEST_MAGIC {
1087 return Err(RdbFileError::InvalidOperation(
1088 "invalid embedded manifest magic".into(),
1089 ));
1090 }
1091 let version = take_u32(bytes, &mut cursor)?;
1092 if version != MANIFEST_VERSION && version != LEGACY_MANIFEST_VERSION {
1093 return Err(RdbFileError::InvalidOperation(format!(
1094 "unsupported embedded manifest version {version}"
1095 )));
1096 }
1097 let wal_region_offset = take_u64(bytes, &mut cursor)?;
1098 let wal_region_bytes = take_u64(bytes, &mut cursor)?;
1099 let wal_recovery_boundary = take_u64(bytes, &mut cursor)?;
1100 let wal_live_bytes = if version == MANIFEST_VERSION {
1101 take_u64(bytes, &mut cursor)?
1102 } else {
1103 wal_recovery_boundary.saturating_sub(wal_region_offset)
1104 };
1105 Ok(EmbeddedRdbManifest {
1106 version,
1107 wal_region_offset,
1108 wal_region_bytes,
1109 wal_recovery_boundary,
1110 wal_live_bytes,
1111 snapshot_offset: take_u64(bytes, &mut cursor)?,
1112 snapshot_bytes: take_u64(bytes, &mut cursor)?,
1113 snapshot_checksum: take_u32(bytes, &mut cursor)?,
1114 created_at_unix_ms: take_u128(bytes, &mut cursor)?,
1115 checksum: stored_checksum,
1116 })
1117}
1118
1119fn trailer_checksum(bytes: &[u8]) -> u32 {
1120 let checksum_offset = bytes.len() - CHECKSUM_LEN;
1121 u32::from_le_bytes(bytes[checksum_offset..].try_into().unwrap())
1122}
1123
1124fn superblock_offset(copy_index: u8) -> RdbFileResult<u64> {
1125 match copy_index {
1126 0 => Ok(EMBEDDED_RDB_SUPERBLOCK_0_OFFSET),
1127 1 => Ok(EMBEDDED_RDB_SUPERBLOCK_1_OFFSET),
1128 _ => Err(RdbFileError::InvalidOperation(format!(
1129 "invalid embedded superblock copy index {copy_index}"
1130 ))),
1131 }
1132}
1133
1134fn write_at(file: &mut File, offset: u64, bytes: &[u8]) -> RdbFileResult<()> {
1135 file.seek(SeekFrom::Start(offset))?;
1136 file.write_all(bytes)?;
1137 Ok(())
1138}
1139
1140fn crash_inject(point: &str) {
1141 if std::env::var(CRASH_INJECT_ENV).ok().as_deref() == Some(point) {
1142 std::process::exit(173);
1143 }
1144 if crate::buggify!(CRASH_INJECT_ENV, point) {
1145 std::process::exit(173);
1146 }
1147}
1148
1149fn now_unix_ms() -> u128 {
1150 SystemTime::now()
1151 .duration_since(UNIX_EPOCH)
1152 .map(|duration| duration.as_millis())
1153 .unwrap_or(0)
1154}
1155
1156fn put_bytes(target: &mut [u8], cursor: &mut usize, value: &[u8]) {
1157 target[*cursor..*cursor + value.len()].copy_from_slice(value);
1158 *cursor += value.len();
1159}
1160
1161fn put_u8(target: &mut [u8], cursor: &mut usize, value: u8) {
1162 target[*cursor] = value;
1163 *cursor += 1;
1164}
1165
1166fn put_u32(target: &mut [u8], cursor: &mut usize, value: u32) {
1167 put_bytes(target, cursor, &value.to_le_bytes());
1168}
1169
1170fn put_u64(target: &mut [u8], cursor: &mut usize, value: u64) {
1171 put_bytes(target, cursor, &value.to_le_bytes());
1172}
1173
1174fn put_u128(target: &mut [u8], cursor: &mut usize, value: u128) {
1175 put_bytes(target, cursor, &value.to_le_bytes());
1176}
1177
1178fn take_bytes<'a>(bytes: &'a [u8], cursor: &mut usize, len: usize) -> RdbFileResult<&'a [u8]> {
1179 let end = cursor.checked_add(len).ok_or_else(|| {
1180 RdbFileError::InvalidOperation("embedded artifact cursor overflow".into())
1181 })?;
1182 if end > bytes.len() {
1183 return Err(RdbFileError::InvalidOperation(
1184 "embedded artifact truncated".into(),
1185 ));
1186 }
1187 let value = &bytes[*cursor..end];
1188 *cursor = end;
1189 Ok(value)
1190}
1191
1192fn take_u8(bytes: &[u8], cursor: &mut usize) -> RdbFileResult<u8> {
1193 Ok(take_bytes(bytes, cursor, 1)?[0])
1194}
1195
1196fn take_u32(bytes: &[u8], cursor: &mut usize) -> RdbFileResult<u32> {
1197 Ok(u32::from_le_bytes(
1198 take_bytes(bytes, cursor, 4)?.try_into().unwrap(),
1199 ))
1200}
1201
1202fn take_u64(bytes: &[u8], cursor: &mut usize) -> RdbFileResult<u64> {
1203 Ok(u64::from_le_bytes(
1204 take_bytes(bytes, cursor, 8)?.try_into().unwrap(),
1205 ))
1206}
1207
1208fn take_u128(bytes: &[u8], cursor: &mut usize) -> RdbFileResult<u128> {
1209 Ok(u128::from_le_bytes(
1210 take_bytes(bytes, cursor, 16)?.try_into().unwrap(),
1211 ))
1212}