Skip to main content

forensic_vfs/
source.rs

1//! [`ImageSource`] — the universal read-only byte edge every layer speaks.
2//!
3//! A positioned-read stream: `read_at(&self, offset, buf)` carries no cursor, so
4//! one source is shared across threads by `&self`. There is deliberately **no
5//! write method** anywhere in this trait — evidence is read-only *by
6//! construction*, not by convention. `Send + Sync` are supertraits, so
7//! [`DynSource`] (`Arc<dyn ImageSource>`) is itself `Send + Sync` and composes
8//! cleanly at every seam.
9
10use std::sync::Arc;
11
12use crate::error::VfsResult;
13
14/// Stable identity for a source, for cache keying and lineage. Assigned by the
15/// engine; a `SubRange`/decrypted/overlay source records this so a block cache
16/// can account by identity rather than by accident of equal offsets.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct SourceId(u64);
19
20impl SourceId {
21    /// The base/root source of a lineage when no engine has assigned one yet.
22    pub const ROOT: SourceId = SourceId(0);
23
24    /// Wrap a raw id.
25    #[must_use]
26    pub const fn new(id: u64) -> Self {
27        Self(id)
28    }
29
30    /// The raw id.
31    #[must_use]
32    pub const fn get(self) -> u64 {
33        self.0
34    }
35}
36
37impl Default for SourceId {
38    fn default() -> Self {
39        Self::ROOT
40    }
41}
42
43/// A borrowed, contiguous view into a source that owns whatever guard keeps it
44/// alive, so a lent slice never dangles over an evicting cache (the round-1 fix
45/// against a bare `&[u8]` tied to `&self`). Derefs to the bytes.
46pub enum SourceView<'a> {
47    /// A borrow of a memory-mapped region.
48    Mmap(&'a [u8]),
49    /// A reference-counted cache block plus the valid sub-range within it.
50    Block(Arc<[u8]>, core::ops::Range<usize>),
51}
52
53impl core::ops::Deref for SourceView<'_> {
54    type Target = [u8];
55    fn deref(&self) -> &[u8] {
56        match self {
57            SourceView::Mmap(s) => s,
58            // The range is set by the producer to a valid sub-slice; fall back to
59            // empty rather than panic if a future caller supplies a bad range.
60            SourceView::Block(arc, r) => arc.get(r.clone()).unwrap_or(&[]),
61        }
62    }
63}
64
65/// One allocated extent `[offset, offset+len)` in a source's address space.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct Extent {
68    pub offset: u64,
69    pub len: u64,
70}
71
72/// The allocated-extent map of a source, so callers skip zero/sparse runs on
73/// TB-scale images. A container reader reports real allocated runs; the default
74/// is one dense extent covering the whole stream.
75#[derive(Debug, Clone, Default, PartialEq, Eq)]
76pub struct Extents {
77    runs: Vec<Extent>,
78}
79
80impl Extents {
81    /// One extent covering `[0, len)` — the "everything is allocated" default.
82    #[must_use]
83    pub fn dense(len: u64) -> Self {
84        if len == 0 {
85            Self { runs: Vec::new() }
86        } else {
87            Self {
88                runs: vec![Extent { offset: 0, len }],
89            }
90        }
91    }
92
93    /// Build from explicit runs.
94    #[must_use]
95    pub fn from_runs(runs: Vec<Extent>) -> Self {
96        Self { runs }
97    }
98
99    /// The runs, in address order as supplied.
100    #[must_use]
101    pub fn runs(&self) -> &[Extent] {
102        &self.runs
103    }
104
105    /// Total allocated bytes across all runs (saturating).
106    #[must_use]
107    pub fn allocated_len(&self) -> u64 {
108        self.runs
109            .iter()
110            .fold(0u64, |acc, r| acc.saturating_add(r.len))
111    }
112
113    /// True when there are no allocated runs.
114    #[must_use]
115    pub fn is_empty(&self) -> bool {
116        self.runs.is_empty()
117    }
118}
119
120/// A read-only, randomly-addressable byte stream: a decoded container, a
121/// partition window, a decrypted volume, a VSS store, a file's data, or a byte
122/// range of any of them. `Send + Sync`; positioned reads only; no writer.
123pub trait ImageSource: Send + Sync {
124    /// Logical size in bytes of this stream.
125    fn len(&self) -> u64;
126
127    /// True when the stream is zero-length.
128    fn is_empty(&self) -> bool {
129        self.len() == 0
130    }
131
132    /// Fill `buf` starting at byte `offset`. Returns bytes read (0 at/after EOF).
133    /// Never panics; a short read past EOF returns the available prefix length.
134    fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult<usize>;
135
136    /// Allocated-extent map. Default = one dense extent covering `[0, len)`.
137    fn extents(&self) -> Extents {
138        Extents::dense(self.len())
139    }
140
141    /// Optional zero-copy fast path. `None` when the backing cannot lend a
142    /// contiguous view (the caller then uses [`ImageSource::read_at`]).
143    fn view(&self, offset: u64, len: usize) -> Option<SourceView<'_>> {
144        let _ = (offset, len);
145        None
146    }
147
148    /// Stable identity for cache keying and lineage.
149    fn source_id(&self) -> SourceId {
150        SourceId::ROOT
151    }
152}
153
154/// The object-safe shared handle used at every composition seam. `Arc`, not
155/// `Box`: a child layer keeps a handle to its parent, and one parent backs many
156/// children (every partition shares the disk source). `Send + Sync` because
157/// [`ImageSource`] requires them.
158pub type DynSource = Arc<dyn ImageSource>;
159
160/// Read exactly `buf.len()` bytes at `offset`, erroring if the stream is too
161/// short — the "give me all of it or fail loud" helper over the short-read
162/// [`ImageSource::read_at`].
163pub fn read_exact_at(src: &dyn ImageSource, offset: u64, buf: &mut [u8]) -> VfsResult<()> {
164    let got = src.read_at(offset, buf)?;
165    if got == buf.len() {
166        Ok(())
167    } else {
168        Err(crate::error::VfsError::OutOfRange {
169            what: "read_exact_at",
170            offset,
171            len: buf.len() as u64,
172            bound: src.len(),
173        })
174    }
175}