Skip to main content

reddb_file/
embedded.rs

1//! Embedded single-file `.rdb` artifact.
2//!
3//! This module models the promoted path where one durable database artifact
4//! carries its superblock pair, internal manifest, WAL reservation, and current
5//! store snapshot inside the `.rdb` file itself.
6
7use 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    /// A named zone of the `.rdb` cannot be trusted, so the store does not
25    /// open. ADR 0074 §2 requires the zone be named and §4 requires the
26    /// operator be pointed at `red salvage` rather than left with a panic.
27    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
69/// The manifest zone is a ping-pong pair, exactly like the superblock zone.
70///
71/// A single in-place manifest could not satisfy "never torn": overwriting it
72/// leaves a window in which the durable superblock still names the old
73/// checksum while the bytes are already the new ones. Publishing into the
74/// *inactive* slot and only then pointing a fresh superblock at it means every
75/// valid superblock always references an intact manifest — pre-update or
76/// post-update, never a mixture.
77pub 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            // The manifest a valid superblock names is always intact: it was
258            // fsynced into an inactive slot before this generation existed. A
259            // checksum failure here is therefore bit rot, not a torn update,
260            // and ADR 0074 §2 says it fails the open by name — never falls
261            // back to a stale root that would resurrect superseded state.
262            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        // Publish the manifest into the slot the live superblock does NOT
343        // reference, and make it durable before any superblock names it. Until
344        // the superblock write below lands, the durable state still roots
345        // through the old manifest slot, which these bytes never touched.
346        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        // The superblock is the WAL authority: appends update it without
394        // rewriting the manifest zone, so a stale manifest wal_live_bytes
395        // would make the scan see an empty region and drop live records.
396        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
526/// The manifest slot the given superblock does not reference.
527///
528/// With two superblock copies and two manifest slots this is always the slot
529/// belonging to the *stale* superblock copy — the same copy the update is about
530/// to overwrite. That coincidence is what makes the pair safe: a crash can only
531/// ever clobber the manifest of a superblock that was already being replaced.
532fn 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    // A superblock that points outside the manifest zone is a misdirected or
555    // forged write; never chase the pointer.
556    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}