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}