Skip to main content

fs_core/
slice.rs

1//! Slice adapters — view a byte sub-range of any `BlockRead` as its own
2//! device. Useful any time you want to feed a fragment of a larger
3//! device to a consumer that expects a whole block source — partition
4//! probes, image-file extents, mmap-style views, fuzzer harnesses.
5//!
6//! Three variants:
7//!
8//! - [`SliceReader`] borrows the parent, lifetime-tied. Cheaper when the
9//!   parent outlives the slice and you can express that statically.
10//! - [`OwnedSlice`] holds an `Arc` to the parent. Use when the parent's
11//!   lifetime can't be expressed in a borrow (FFI handles, slice handed
12//!   across thread boundaries, etc.).
13//! - [`OwnedRwSlice`] holds an `Arc<dyn BlockDevice>` and propagates
14//!   writes to the parent.
15//!
16//! The first two are strictly read-only: the default `Err(ReadOnly)`
17//! write path from [`BlockDevice`] applies.
18//!
19//! # Which error an out-of-range request gets
20//!
21//! All three share one range check — `SliceGeometry::rebase` — and it
22//! answers in two different currencies depending on the direction of the
23//! request:
24//!
25//! | request outside `[0, length)` | error |
26//! |---|---|
27//! | read  | [`Error::ShortRead`] with `got: 0` |
28//! | write | [`Error::OutOfBounds`] |
29//!
30//! The asymmetry is deliberate. A slice exists to be substitutable for a
31//! real device of size `length`, and a real device — [`FileDevice`] —
32//! answers a read that begins at or past its end with exactly
33//! `ShortRead { offset, want, got: 0 }`. A slice that answered
34//! `OutOfBounds` would be distinguishable from the thing it stands in
35//! for, and every caller that already handles end-of-device would need a
36//! second arm to cope with slices. Writes have no partial-write variant
37//! to stay consistent with, and a caller that overran a write needs the
38//! device size in order to clamp and retry — which is what
39//! [`Error::OutOfBounds`] carries and [`Error::ShortRead`] does not.
40//!
41//! The match is on the variant, not on `got`. A slice refuses an
42//! out-of-range read before it touches the parent, so it reports `got: 0`
43//! and leaves the buffer untouched — including for a read that begins
44//! inside the slice and runs off its end, where [`FileDevice`] would have
45//! copied the readable prefix and reported its length. `got` counts bytes
46//! actually delivered, and a slice delivers none.
47//!
48//! This governs the slice's own range only. A request that *is* inside
49//! `[0, length)` is forwarded to the parent, and whatever the parent says
50//! about it — including [`Error::OutOfBounds`] from a container reader
51//! that knows its virtual size — comes back unchanged.
52//!
53//! # A slice cannot report more device than its parent holds
54//!
55//! All three constructors ask the parent its size and CLAMP `length` to
56//! what is actually there — see [`window_on_parent`] for the rule and
57//! why it clamps rather than refuses. So `size_bytes()` is the truth
58//! about how much is readable, not a number the caller asserted.
59//!
60//! That is load-bearing rather than tidy. `size_bytes()` is "used for
61//! bounds checks" ([`BlockRead::size_bytes`]), and a driver mounted on a
62//! slice sizes its own structures from it. A slice that claimed a
63//! megabyte over a hundred-byte parent kept both promises of the section
64//! above and broke them in the same breath: a read inside the *declared*
65//! length but past the *real* end was forwarded, and the parent — being
66//! a real device — answered with a `ShortRead` carrying its own absolute
67//! offset and a non-zero `got`, having already copied the readable prefix
68//! into the caller's buffer. Substitutable for a real device of size
69//! `length` is exactly what that is not.
70//!
71//! A slice whose `start` is at or past the parent's end has nothing
72//! behind it at all. The constructors are infallible and give it
73//! `length = 0`, which behaves as a zero-byte device does: every read is
74//! `ShortRead { got: 0 }` and every write is `OutOfBounds { size: 0 }`.
75//! The C ABI, which can afford to be fallible, refuses it instead —
76//! `fs_core_device_slice_ro`/`_rw` return NULL with a message.
77//!
78//! [`FileDevice`]: crate::FileDevice
79
80use crate::block::{BlockDevice, BlockRead};
81use crate::error::{Error, Result};
82use std::sync::Arc;
83
84/// How much of the window `[start, start + length)` is actually on a
85/// parent of `parent_size` bytes, or `None` when the window begins at or
86/// past the parent's end and there is nothing on it to slice.
87///
88/// A slice's geometry comes from a partition table, and a partition
89/// table comes off the disk, so a window that claims to start or end
90/// past the device is an ordinary thing to be handed -- and not only
91/// from a hostile image. A `dd` of the first N gigabytes of a disk, or a
92/// table left stale after the volume was shrunk, both produce a last
93/// partition that runs off the end.
94///
95/// So the length is CLAMPED rather than the slice refused. Refusing it
96/// takes away the one thing someone with a truncated image wants, which
97/// is to read what is still there. What must not happen is the slice
98/// reporting more device than exists, because the driver stacked on it
99/// sizes its own structures from that answer.
100///
101/// Clamping also closes the arithmetic hole in the slices' shared
102/// rebasing step as a side effect, and closes it at the root.
103/// `start + offset + len`
104/// can only leave a `u64` if `start + length` does, and `length` is now
105/// at most `parent_size - start`.
106pub fn window_on_parent(parent_size: u64, start: u64, length: u64) -> Option<u64> {
107    if start >= parent_size {
108        return None;
109    }
110    Some(length.min(parent_size - start))
111}
112
113/// Where a slice sits on its parent, and the one bounds rule the three
114/// slice types share.
115///
116/// The public slice types differ only in how they hold the parent and
117/// whether writes propagate. The geometry, the range check and the choice
118/// of error are identical across all of them, so they live here — one
119/// definition to read, one place to change.
120#[derive(Clone, Copy)]
121struct SliceGeometry {
122    start: u64,
123    length: u64,
124}
125
126impl SliceGeometry {
127    /// `length` is CLAMPED to what `parent_size` can back — see
128    /// [`window_on_parent`]. A window beginning at or past the parent's
129    /// end becomes a zero-length slice, which is what it is.
130    ///
131    /// Taking the parent's size here rather than on each read is
132    /// deliberate: [`BlockRead::size_bytes`] must not change for the
133    /// life of a device, so one call at construction is the whole
134    /// answer, and `length` is then a fact instead of a claim.
135    fn new(parent_size: u64, start: u64, length: u64) -> Self {
136        Self {
137            start,
138            length: window_on_parent(parent_size, start, length).unwrap_or(0),
139        }
140    }
141
142    /// Parent offset corresponding to `offset`, or `None` when
143    /// `[offset, offset + len)` is not wholly inside `[0, length)`.
144    ///
145    /// This asks the parent nothing — the parent was asked once, in
146    /// [`SliceGeometry::new`], and `length` is its answer. The comment
147    /// here used to claim a second check "when the rebased offset would
148    /// not fit on the parent at all", which no code performed and no
149    /// parent was in scope to perform: it was a `u64` overflow guard
150    /// being described as a bounds check against the device.
151    ///
152    /// Both additions stay checked, `start + offset` included. That one
153    /// used to be deliberate, on the argument that a slice built with a
154    /// nonsense `start` would "overflow here rather than quietly
155    /// reading some other part of the parent" -- which holds only while
156    /// `overflow-checks` is on, and it is off in the release profile
157    /// these crates ship. In release the addition wrapped, and the wrap
158    /// did precisely the thing the argument said it avoided: a slice
159    /// starting at 2^63 and 5000 bytes long returned `Ok` and the
160    /// parent's bytes from offset 5000.
161    ///
162    /// Both are now unreachable through the public API, because the
163    /// clamp makes `start + length <= parent_size` and every accepted
164    /// `offset + len` is at most `length`. They are kept as the last
165    /// line of defence for a parent that violates the `size_bytes`
166    /// stability contract and shrinks under a live slice; the guard that
167    /// is actually load-bearing, and tested, is the clamp.
168    fn rebase(&self, offset: u64, len: u64) -> Option<u64> {
169        let end = offset.checked_add(len)?;
170        if end > self.length {
171            return None;
172        }
173        self.start.checked_add(offset)
174    }
175
176    /// Bounds-check a read and rebase it onto the parent.
177    ///
178    /// Out of range is [`Error::ShortRead`] with `got: 0` — the same
179    /// answer a real device of size `length` gives for a read beginning
180    /// at or past its end. See the module docs for why.
181    fn rebase_read(&self, offset: u64, len: usize) -> Result<u64> {
182        self.rebase(offset, len as u64).ok_or(Error::ShortRead {
183            offset,
184            want: len,
185            got: 0,
186        })
187    }
188
189    /// Bounds-check a write and rebase it onto the parent.
190    ///
191    /// Out of range is [`Error::OutOfBounds`]: nothing was written, and
192    /// the caller is handed the slice's size so it can clamp and retry.
193    fn rebase_write(&self, offset: u64, len: usize) -> Result<u64> {
194        self.rebase(offset, len as u64).ok_or(Error::OutOfBounds {
195            offset,
196            len: len as u64,
197            size: self.length,
198        })
199    }
200}
201
202/// Borrowed slice of a parent `BlockRead`.
203///
204/// `read_at(0, …)` reads `start` of the parent. Reads outside
205/// `[0, length)` return [`Error::ShortRead`] with `got: 0`.
206pub struct SliceReader<'a> {
207    parent: &'a (dyn BlockRead + 'a),
208    geom: SliceGeometry,
209}
210
211impl<'a> SliceReader<'a> {
212    /// `length` is clamped to what the parent can back, so
213    /// `size_bytes()` never exceeds it. See [`window_on_parent`].
214    pub fn new(parent: &'a (dyn BlockRead + 'a), start: u64, length: u64) -> Self {
215        let geom = SliceGeometry::new(parent.size_bytes(), start, length);
216        Self { parent, geom }
217    }
218
219    /// Byte offset of this slice on the parent device.
220    pub fn start(&self) -> u64 {
221        self.geom.start
222    }
223
224    /// Length of this slice in bytes (== `size_bytes()`).
225    pub fn length(&self) -> u64 {
226        self.geom.length
227    }
228}
229
230impl<'a> BlockRead for SliceReader<'a> {
231    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
232        let at = self.geom.rebase_read(offset, buf.len())?;
233        self.parent.read_at(at, buf)
234    }
235
236    fn size_bytes(&self) -> u64 {
237        self.geom.length
238    }
239}
240
241/// Slices are read-only by default — even where the parent is writable,
242/// slicing is almost always paired with a read-only inspection or
243/// dispatch workflow.
244impl<'a> BlockDevice for SliceReader<'a> {}
245
246/// Owned slice over an `Arc<dyn BlockRead>`. Use when the parent's
247/// lifetime can't be expressed in a borrow — e.g. when the slice is
248/// handed across an FFI boundary or stored in a long-lived struct.
249///
250/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`.
251pub struct OwnedSlice {
252    parent: Arc<dyn BlockRead>,
253    geom: SliceGeometry,
254}
255
256impl OwnedSlice {
257    /// `length` is clamped to what the parent can back, so
258    /// `size_bytes()` never exceeds it. See [`window_on_parent`].
259    pub fn new(parent: Arc<dyn BlockRead>, start: u64, length: u64) -> Self {
260        let geom = SliceGeometry::new(parent.size_bytes(), start, length);
261        Self { parent, geom }
262    }
263
264    /// Byte offset of this slice on the parent device.
265    pub fn start(&self) -> u64 {
266        self.geom.start
267    }
268
269    /// Length of this slice in bytes (== `size_bytes()`).
270    pub fn length(&self) -> u64 {
271        self.geom.length
272    }
273}
274
275impl BlockRead for OwnedSlice {
276    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
277        let at = self.geom.rebase_read(offset, buf.len())?;
278        self.parent.read_at(at, buf)
279    }
280
281    fn size_bytes(&self) -> u64 {
282        self.geom.length
283    }
284}
285
286/// Same rationale as `SliceReader`: read-only by default.
287impl BlockDevice for OwnedSlice {}
288
289/// Owned, read-WRITE slice over an `Arc<dyn BlockDevice>`. Use when the
290/// parent is writable and the slice should propagate writes (e.g. an
291/// individual partition handed to a filesystem driver).
292///
293/// Reads outside `[0, length)` return [`Error::ShortRead`] with `got: 0`;
294/// writes outside it return [`Error::OutOfBounds`]. The two directions
295/// differ on purpose — see the module docs.
296pub struct OwnedRwSlice {
297    parent: Arc<dyn BlockDevice>,
298    geom: SliceGeometry,
299}
300
301impl OwnedRwSlice {
302    /// `length` is clamped to what the parent can back, so
303    /// `size_bytes()` never exceeds it and a write past the parent's end
304    /// is [`Error::OutOfBounds`] from this slice rather than whatever
305    /// the parent makes of an address beyond itself. See
306    /// [`window_on_parent`].
307    pub fn new(parent: Arc<dyn BlockDevice>, start: u64, length: u64) -> Self {
308        let geom = SliceGeometry::new(parent.size_bytes(), start, length);
309        Self { parent, geom }
310    }
311
312    /// Byte offset of this slice on the parent device.
313    pub fn start(&self) -> u64 {
314        self.geom.start
315    }
316
317    /// Length of this slice in bytes (== `size_bytes()`).
318    pub fn length(&self) -> u64 {
319        self.geom.length
320    }
321}
322
323impl BlockRead for OwnedRwSlice {
324    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
325        let at = self.geom.rebase_read(offset, buf.len())?;
326        self.parent.read_at(at, buf)
327    }
328
329    fn size_bytes(&self) -> u64 {
330        self.geom.length
331    }
332}
333
334impl BlockDevice for OwnedRwSlice {
335    /// Range first, writability second: a write that is both out of range
336    /// and aimed at a read-only parent reports [`Error::OutOfBounds`],
337    /// not [`Error::ReadOnly`]. The range is a property of this slice and
338    /// is knowable without asking the parent anything, so it is the more
339    /// specific of the two answers.
340    fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
341        let at = self.geom.rebase_write(offset, buf.len())?;
342        if !self.parent.is_writable() {
343            return Err(Error::ReadOnly);
344        }
345        self.parent.write_at(at, buf)
346    }
347
348    fn flush(&self) -> Result<()> {
349        self.parent.flush()
350    }
351
352    fn is_writable(&self) -> bool {
353        self.parent.is_writable()
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::test_device::{Bytes, RwBytes};
361    use std::sync::Mutex;
362
363    #[test]
364    fn slice_reader_rebases_offsets() {
365        let mut v = vec![0u8; 4096];
366        v[2000..2004].copy_from_slice(&[0xAB, 0xCD, 0xEF, 0x01]);
367        let dev = Bytes(Mutex::new(v));
368
369        let slice = SliceReader::new(&dev, 2000, 4);
370        assert_eq!(slice.size_bytes(), 4);
371        assert_eq!(slice.start(), 2000);
372        assert_eq!(slice.length(), 4);
373
374        let mut buf = [0u8; 4];
375        slice.read_at(0, &mut buf).unwrap();
376        assert_eq!(buf, [0xAB, 0xCD, 0xEF, 0x01]);
377    }
378
379    /// A slice's geometry comes from a partition table, and a partition
380    /// table comes off the disk. A start and a length that add up past
381    /// 2^64 are an ordinary thing to be handed.
382    ///
383    /// In a release build the rebasing addition wrapped, so a read at an
384    /// offset inside the slice's declared length landed somewhere else
385    /// on the parent entirely -- and came back `Ok`, with those bytes,
386    /// as though they were the slice's own.
387    ///
388    /// The `checked_add` in `rebase` is no longer what stops this: the
389    /// constructor's clamp gets there first, and a start of 2^63 over a
390    /// 64 KiB parent now leaves nothing to read at all. Both assertions
391    /// are kept -- the size, which is the guard now in force, and the
392    /// bytes, which are what went wrong.
393    #[test]
394    fn a_slice_whose_start_plus_offset_leaves_the_parent_reads_nothing() {
395        let mut v = vec![0u8; 64 * 1024];
396        v[5000..5008].copy_from_slice(b"SECRET!!");
397        let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(v)));
398
399        // A GPT entry of starting_lba = 2^54 and ending_lba = 2^55 + 99
400        // produces exactly this.
401        let slice = OwnedSlice::new(dev, 1 << 63, (1 << 63) + 51200);
402        assert_eq!(
403            slice.size_bytes(),
404            0,
405            "the window begins past the parent, so none of it is there"
406        );
407        let mut buf = [0u8; 8];
408        let inside_the_declared_length = (1u64 << 63) + 5000;
409
410        let outcome = slice.read_at(inside_the_declared_length, &mut buf);
411        assert!(
412            outcome.is_err(),
413            "the read succeeded and returned {:?}, which is the parent's \
414             bytes from offset 5000",
415            std::str::from_utf8(&buf)
416        );
417        assert_ne!(&buf, b"SECRET!!");
418    }
419
420    #[test]
421    fn slice_reader_rejects_out_of_bounds() {
422        let dev = Bytes(Mutex::new(vec![0u8; 4096]));
423        let slice = SliceReader::new(&dev, 0, 16);
424        let mut buf = [0u8; 8];
425        match slice.read_at(12, &mut buf) {
426            Err(Error::ShortRead { .. }) => {}
427            other => panic!("expected ShortRead, got {other:?}"),
428        }
429    }
430
431    #[test]
432    fn owned_slice_works_through_arc() {
433        let mut v = vec![0u8; 4096];
434        v[100..104].copy_from_slice(&[0x11, 0x22, 0x33, 0x44]);
435        let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(v)));
436
437        let slice = OwnedSlice::new(dev, 100, 4);
438        assert_eq!(slice.size_bytes(), 4);
439        let mut buf = [0u8; 4];
440        slice.read_at(0, &mut buf).unwrap();
441        assert_eq!(buf, [0x11, 0x22, 0x33, 0x44]);
442    }
443
444    #[test]
445    fn slices_reject_writes_via_blockdevice_default() {
446        let dev = Bytes(Mutex::new(vec![0u8; 16]));
447        let slice = SliceReader::new(&dev, 0, 8);
448        let err = BlockDevice::write_at(&slice, 0, &[1u8; 4]).unwrap_err();
449        assert!(matches!(err, Error::ReadOnly));
450    }
451
452    #[test]
453    fn owned_slice_accessors_report_geometry() {
454        let dev: Arc<dyn BlockRead> = Arc::new(Bytes(Mutex::new(vec![0u8; 4096])));
455        let slice = OwnedSlice::new(dev, 512, 256);
456        assert_eq!(slice.start(), 512);
457        assert_eq!(slice.length(), 256);
458        assert_eq!(slice.size_bytes(), 256);
459    }
460
461    #[test]
462    fn owned_rw_slice_accessors_report_geometry() {
463        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
464        let slice = OwnedRwSlice::new(dev, 16, 32);
465        assert_eq!(slice.start(), 16);
466        assert_eq!(slice.length(), 32);
467        assert_eq!(slice.size_bytes(), 32);
468        assert!(slice.is_writable());
469    }
470
471    #[test]
472    fn owned_rw_slice_rebases_reads_and_writes() {
473        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
474        let slice = OwnedRwSlice::new(dev.clone(), 16, 32);
475
476        // Write through the slice lands at parent offset 16.
477        slice.write_at(0, &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
478        let mut buf = [0u8; 4];
479        slice.read_at(0, &mut buf).unwrap();
480        assert_eq!(buf, [0xDE, 0xAD, 0xBE, 0xEF]);
481
482        // Confirm rebasing against the parent directly.
483        let mut pbuf = [0u8; 4];
484        dev.read_at(16, &mut pbuf).unwrap();
485        assert_eq!(pbuf, [0xDE, 0xAD, 0xBE, 0xEF]);
486    }
487
488    #[test]
489    fn owned_rw_slice_rejects_out_of_bounds_write() {
490        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
491        let slice = OwnedRwSlice::new(dev, 0, 8);
492        match slice.write_at(6, &[0u8; 4]) {
493            Err(Error::OutOfBounds { .. }) => {}
494            other => panic!("expected OutOfBounds, got {other:?}"),
495        }
496    }
497
498    /// The bounds rule is direction-dependent by design: one slice, one
499    /// out-of-range span, two different errors. Pinned here so the
500    /// asymmetry cannot be "tidied up" into consistency without someone
501    /// deciding to — the reasoning is in the module docs.
502    #[test]
503    fn same_out_of_range_span_is_short_read_for_a_read_and_out_of_bounds_for_a_write() {
504        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 64])));
505        let slice = OwnedRwSlice::new(dev, 16, 8);
506
507        let mut buf = [0u8; 4];
508        match slice.read_at(6, &mut buf) {
509            Err(Error::ShortRead { offset, want, got }) => {
510                assert_eq!((offset, want, got), (6, 4, 0));
511            }
512            other => panic!("expected ShortRead, got {other:?}"),
513        }
514
515        match slice.write_at(6, &[0u8; 4]) {
516            Err(Error::OutOfBounds { offset, len, size }) => {
517                assert_eq!((offset, len, size), (6, 4, 8));
518            }
519            other => panic!("expected OutOfBounds, got {other:?}"),
520        }
521    }
522
523    /// The rule itself, at its edges. Clamp where the window merely runs
524    /// off the end; `None` only where it begins at or past the end and
525    /// there is nothing to slice.
526    #[test]
527    fn window_on_parent_clamps_the_length_and_refuses_only_a_start_past_the_end() {
528        // Wholly inside: untouched.
529        assert_eq!(window_on_parent(1024, 0, 1024), Some(1024));
530        assert_eq!(window_on_parent(1024, 512, 512), Some(512));
531        assert_eq!(window_on_parent(1024, 1023, 1), Some(1));
532
533        // Running off the end: as much of it as is there. A `dd` of the
534        // first part of a disk, or a table left stale after a shrink.
535        assert_eq!(window_on_parent(1024, 512, 513), Some(512));
536        assert_eq!(window_on_parent(1024, 0, u64::MAX), Some(1024));
537
538        // Beginning at or past the end: nothing on the parent to slice.
539        assert_eq!(window_on_parent(1024, 1024, 1), None);
540        assert_eq!(window_on_parent(1024, 4096, 1), None);
541        assert_eq!(window_on_parent(0, 0, 8), None);
542
543        // The pair a GPT entry of starting_lba 2^54 and ending_lba
544        // 2^55 + 99 produces: the sum leaves a u64 entirely, and the
545        // start alone is already past the parent.
546        assert_eq!(
547            window_on_parent(64 * 1024, 1 << 63, (1 << 63) + 51200),
548            None
549        );
550
551        // The arithmetic case from the top of the address space. The
552        // clamp is what keeps `start + offset + len` inside a u64: 4
553        // bytes rebase to exactly `u64::MAX`, and the requested 8 would
554        // not have.
555        assert_eq!(window_on_parent(u64::MAX, u64::MAX - 4, 8), Some(4));
556    }
557
558    /// `size_bytes()` is what bounds checks are done against, so a
559    /// borrowed slice must not report a window its parent cannot back.
560    ///
561    /// The read assertion is on the ShortRead's FIELDS, not on
562    /// `is_err()`. An unclamped slice rebases 45 to 95 and the parent
563    /// refuses that too -- the error arrives either way. What tells the
564    /// two apart is whose bounds were consulted: the slice's own offset
565    /// and `got: 0`, or the parent's absolute 95 and the five bytes it
566    /// could have delivered.
567    #[test]
568    fn a_borrowed_slice_cannot_claim_more_than_its_parent_holds() {
569        let dev = Bytes::new(vec![0xEE; 100]);
570        let slice = SliceReader::new(&dev, 50, 100);
571
572        assert_eq!(slice.size_bytes(), 50);
573        assert_eq!(slice.length(), 50);
574        assert_eq!(
575            slice.start(),
576            50,
577            "the start is not clamped, only the length"
578        );
579
580        let mut buf = [0u8; 8];
581        match slice.read_at(45, &mut buf) {
582            Err(Error::ShortRead { offset, want, got }) => {
583                assert_eq!((offset, want, got), (45, 8, 0));
584            }
585            other => panic!("expected the slice's own ShortRead, got {other:?}"),
586        }
587        assert_eq!(buf, [0u8; 8], "a refused read leaves the buffer alone");
588    }
589
590    /// The same for the `Arc` variant, which is the one the C ABI and
591    /// the partition walkers reach.
592    #[test]
593    fn an_owned_slice_cannot_claim_more_than_its_parent_holds() {
594        let mut v = vec![0u8; 100];
595        v[50..58].copy_from_slice(b"LASTHALF");
596        let dev: Arc<dyn BlockRead> = Arc::new(Bytes::new(v));
597        let slice = OwnedSlice::new(dev, 50, 100);
598
599        assert_eq!(slice.size_bytes(), 50);
600        assert_eq!(slice.length(), 50);
601
602        // What is there still reads.
603        let mut buf = [0u8; 8];
604        slice.read_at(0, &mut buf).unwrap();
605        assert_eq!(&buf, b"LASTHALF");
606
607        match slice.read_at(45, &mut buf) {
608            Err(Error::ShortRead { offset, want, got }) => {
609                assert_eq!((offset, want, got), (45, 8, 0));
610            }
611            other => panic!("expected the slice's own ShortRead, got {other:?}"),
612        }
613    }
614
615    /// The write direction has its own currency, and an over-long slice
616    /// lost it: the write was inside the declared length, so it was
617    /// forwarded, and the caller got whatever the parent makes of an
618    /// address beyond itself -- a `ShortRead` from a WRITE, in the case
619    /// of the in-memory double, and a silently extended file in the case
620    /// of `FileDevice`, which seeks and writes without a bounds check.
621    #[test]
622    fn an_rw_slice_refuses_a_write_past_its_parents_end_as_out_of_bounds() {
623        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes::new(vec![0u8; 64]));
624        let slice = OwnedRwSlice::new(dev.clone(), 32, 4096);
625
626        assert_eq!(slice.size_bytes(), 32);
627
628        // Inside the clamped window still writes through.
629        slice.write_at(0, &[0xAB; 4]).unwrap();
630        let mut pbuf = [0u8; 4];
631        dev.read_at(32, &mut pbuf).unwrap();
632        assert_eq!(pbuf, [0xAB; 4]);
633
634        match slice.write_at(32, &[0xCD; 8]) {
635            Err(Error::OutOfBounds { offset, len, size }) => {
636                assert_eq!((offset, len, size), (32, 8, 32));
637            }
638            other => panic!("expected the slice's own OutOfBounds, got {other:?}"),
639        }
640        assert_eq!(dev.size_bytes(), 64, "the parent did not grow");
641    }
642
643    /// A window that begins at or past the parent's end is a zero-byte
644    /// device, not a window onto somewhere else.
645    #[test]
646    fn a_slice_starting_past_its_parents_end_is_empty() {
647        let mut v = vec![0u8; 100];
648        v[0..8].copy_from_slice(b"NOTYOURS");
649        let dev: Arc<dyn BlockRead> = Arc::new(Bytes::new(v));
650        let slice = OwnedSlice::new(dev, 200, 64);
651
652        assert_eq!(slice.size_bytes(), 0);
653
654        let mut buf = [0u8; 8];
655        match slice.read_at(0, &mut buf) {
656            Err(Error::ShortRead { offset, want, got }) => {
657                assert_eq!((offset, want, got), (0, 8, 0));
658            }
659            other => panic!("expected ShortRead, got {other:?}"),
660        }
661        assert_eq!(buf, [0u8; 8]);
662    }
663
664    #[test]
665    fn owned_rw_slice_flush_delegates_to_parent() {
666        let dev: Arc<dyn BlockDevice> = Arc::new(RwBytes(Mutex::new(vec![0u8; 8])));
667        let slice = OwnedRwSlice::new(dev, 0, 8);
668        // Default `flush` on RwBytes is a no-op success; the slice forwards it.
669        slice.flush().unwrap();
670    }
671}