sidereon_core/artifact_bytes.rs
1//! Backing storage for readers over large binary artifacts.
2//!
3//! The terrain store and the precise-interpolant store are both read by
4//! indexing into a byte span: construction parses only the header, datum tag,
5//! and tile/segment index, and every lookup addresses payload by offset. Neither
6//! reader holds a reference into its own bytes, so the bytes can be owned,
7//! borrowed, or memory-mapped without any of them being a self-referential
8//! struct - the span is derived on demand from whichever backing is present.
9//!
10//! That is what makes the mapped variant safe to add: there is no interior
11//! pointer to keep valid, no drop-order invariant hiding in field declaration
12//! order, and no `unsafe` at any interface boundary.
13//!
14//! # Why mapping matters more than the copy
15//!
16//! Avoiding one `memcpy` is the smaller half. A memory map is demand-paged, so a
17//! reader that queries a geographically local region faults in the handful of
18//! pages covering those tiles and never touches the rest of the file. A
19//! constructor that copies - however the bytes arrive - forfeits that and pays
20//! for the whole artifact on every open. For a 30+ GB terrain store that is the
21//! difference between a working process and one that cannot start.
22
23use std::borrow::Cow;
24
25/// Who computed the content digest a handle carries.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum DigestProvenance {
28 /// The library hashed the payload at open or during verification and the
29 /// digest is its own measurement.
30 Verified,
31 /// The caller asserted the digest at open; the library recorded it and
32 /// skipped the payload hash. An attested handle never reports this digest
33 /// without also reporting its provenance.
34 Attested,
35}
36
37/// Where a reader's bytes live.
38///
39/// `Mapped` is only available with the `mmap` feature, so the default build
40/// carries no additional dependency and targets where mapping is meaningless
41/// (wasm) simply do not enable it.
42#[derive(Debug, Clone)]
43pub enum ArtifactBytes<'a> {
44 /// A span the caller owns and keeps alive.
45 Borrowed(&'a [u8]),
46 /// A vector this reader owns.
47 Owned(Vec<u8>),
48 /// A read-only memory map this reader owns.
49 ///
50 /// Shared behind an `Arc` so the reader stays cheap to clone; a map is a
51 /// kernel-level resource and duplicating it per clone would be wasteful.
52 #[cfg(feature = "mmap")]
53 Mapped(std::sync::Arc<memmap2::Mmap>),
54}
55
56impl<'a> ArtifactBytes<'a> {
57 /// Borrow the artifact bytes.
58 #[must_use]
59 pub fn as_slice(&self) -> &[u8] {
60 match self {
61 Self::Borrowed(bytes) => bytes,
62 Self::Owned(bytes) => bytes.as_slice(),
63 #[cfg(feature = "mmap")]
64 Self::Mapped(map) => &map[..],
65 }
66 }
67
68 /// Whether these bytes are a memory map rather than a copy in process
69 /// memory.
70 ///
71 /// Exposed so a caller - or a test - can assert that a path-based open
72 /// actually mapped the file instead of reading it. A change that quietly
73 /// relocated the copy would otherwise be indistinguishable from a fix.
74 #[must_use]
75 pub fn is_memory_mapped(&self) -> bool {
76 #[cfg(feature = "mmap")]
77 {
78 matches!(self, Self::Mapped(_))
79 }
80 #[cfg(not(feature = "mmap"))]
81 {
82 false
83 }
84 }
85}
86
87impl AsRef<[u8]> for ArtifactBytes<'_> {
88 fn as_ref(&self) -> &[u8] {
89 self.as_slice()
90 }
91}
92
93impl<'a> From<Cow<'a, [u8]>> for ArtifactBytes<'a> {
94 fn from(bytes: Cow<'a, [u8]>) -> Self {
95 match bytes {
96 Cow::Borrowed(bytes) => Self::Borrowed(bytes),
97 Cow::Owned(bytes) => Self::Owned(bytes),
98 }
99 }
100}
101
102/// Map a file read-only.
103///
104/// These artifacts are content-addressed and are mounted read-only where they
105/// are deployed, so the map is never opened for writing.
106///
107/// # Safety of the underlying map
108///
109/// `memmap2::Mmap::map` is unsafe because the mapped file can be modified by
110/// another process, which would change bytes under the reader. The contract
111/// here is the same one the format already relies on: an artifact is
112/// content-addressed and immutable once published. A caller that maps a file
113/// somebody else is concurrently rewriting has a corrupt read either way; the
114/// map does not introduce that hazard, it inherits it.
115#[cfg(feature = "mmap")]
116pub fn map_file_read_only(path: &std::path::Path) -> std::io::Result<ArtifactBytes<'static>> {
117 let file = std::fs::File::open(path)?;
118 // SAFETY: opened read-only, and the artifact is immutable once published -
119 // see the note above.
120 let map = unsafe { memmap2::Mmap::map(&file)? };
121 Ok(ArtifactBytes::Mapped(std::sync::Arc::new(map)))
122}