Skip to main content

mapping/
file.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::reader::{BlockService, read_aligned_range};
6use crate::{
7    BLOCK_SIZE, ENCRYPTION_KEY_SIZE, Extents, MappingCommand, NullPageRequest, PageRequest,
8    RawMappingCommand,
9};
10use anyhow::{Error, anyhow, bail};
11use blob_metadata::{BlobFormat, BlobMetadata, MerkleLeaves};
12use byteorder::{LittleEndian, ReadBytesExt};
13use delivery_blob::compression::{
14    CompressionAlgorithm, CompressionInfo, DataBuffer, StreamingDecompressor,
15};
16use fuchsia_sync::Mutex;
17use futures::channel::oneshot;
18use fxfs_crypto::{Cipher, FxfsCipher, UnwrappedKey};
19use std::borrow::Borrow;
20use std::cmp::min;
21use std::collections::hash_map::{Entry, HashMap};
22use std::ops::{ControlFlow, Range};
23use std::sync::Arc;
24use storage_profile::{FileMessage, Message as _};
25use vmo_fifo::Message;
26use zx::sys::zx_page_request_command_t::ZX_PAGER_VMO_READ;
27
28/// Target readahead size (128 KiB), matching Fxfs.
29///
30/// Uncompressed and encrypted files always use this size. For compressed files, the effective size
31/// is adjusted so that each readahead window aligns to a whole number of compression chunks.
32pub const TARGET_READ_AHEAD_SIZE: u64 = 128 * 1024;
33
34/// Default Merkle verification alignment (32 KiB) for uncompressed blobs, matching standard
35/// compressed blob chunk sizes.
36pub const DEFAULT_VERIFICATION_ALIGNMENT: u64 = 32 * 1024;
37
38/// Calculates the readahead size for a given `compression_chunk_size`, rounding down
39/// `target_read_ahead_size` to a multiple of `compression_chunk_size`, or returning
40/// `compression_chunk_size` if it is larger than `target_read_ahead_size`.
41fn read_ahead_size_for_chunk_size(compression_chunk_size: u64, target_read_ahead_size: u64) -> u64 {
42    if compression_chunk_size >= target_read_ahead_size {
43        compression_chunk_size
44    } else {
45        (target_read_ahead_size / compression_chunk_size) * compression_chunk_size
46    }
47}
48
49/// Transformation applied to stored data before it can be presented to the reader.
50#[derive(Default)]
51pub enum Transform {
52    /// The file is stored uncompressed and unencrypted.
53    #[default]
54    None,
55    /// The file is stored compressed with chunked metadata.
56    Compressed(CompressionInfo),
57    /// The file is stored encrypted with a cipher.
58    Encrypted(Arc<dyn Cipher>),
59}
60
61impl std::fmt::Debug for Transform {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            Self::None => write!(f, "Transform::None"),
65            Self::Compressed(_) => write!(f, "Transform::Compressed(..)"),
66            Self::Encrypted(cipher) => f.debug_tuple("Transform::Encrypted").field(cipher).finish(),
67        }
68    }
69}
70
71impl From<Option<CompressionInfo>> for Transform {
72    fn from(info: Option<CompressionInfo>) -> Self {
73        match info {
74            Some(info) => Self::Compressed(info),
75            None => Self::None,
76        }
77    }
78}
79
80impl From<CompressionInfo> for Transform {
81    fn from(info: CompressionInfo) -> Self {
82        Self::Compressed(info)
83    }
84}
85
86impl From<Arc<dyn Cipher>> for Transform {
87    fn from(cipher: Arc<dyn Cipher>) -> Self {
88        Self::Encrypted(cipher)
89    }
90}
91
92#[derive(Clone)]
93struct FileCompression(Arc<File>);
94
95impl Borrow<CompressionInfo> for FileCompression {
96    fn borrow(&self) -> &CompressionInfo {
97        self.0.compression_info().expect("compression_info missing")
98    }
99}
100
101/// A mapped file containing extents and an optional transformation (compression or encryption).
102pub struct File {
103    extents: Extents,
104    uncompressed_size: u64,
105    transform: Transform,
106}
107
108impl File {
109    pub fn new(extents: Extents, uncompressed_size: u64, transform: Transform) -> Self {
110        Self { extents, uncompressed_size, transform }
111    }
112
113    /// Returns the extents mapping logical offsets to device offsets.
114    pub fn extents(&self) -> &Extents {
115        &self.extents
116    }
117
118    /// Returns the uncompressed size of the file in bytes.
119    pub fn uncompressed_size(&self) -> u64 {
120        self.uncompressed_size
121    }
122
123    /// Returns the transform applied to the file if compressed or encrypted.
124    pub fn transform(&self) -> &Transform {
125        &self.transform
126    }
127
128    /// Returns decompression metadata if the file is compressed.
129    pub fn compression_info(&self) -> Option<&CompressionInfo> {
130        match &self.transform {
131            Transform::Compressed(info) => Some(info),
132            _ => None,
133        }
134    }
135
136    /// Returns the cipher if the file is encrypted.
137    pub fn cipher(&self) -> Option<&Arc<dyn Cipher>> {
138        match &self.transform {
139            Transform::Encrypted(cipher) => Some(cipher),
140            _ => None,
141        }
142    }
143
144    /// Returns the read-ahead window size in bytes for this file.
145    pub fn read_ahead_size(&self) -> u64 {
146        match &self.transform {
147            Transform::Compressed(info) => {
148                read_ahead_size_for_chunk_size(info.chunk_size(), TARGET_READ_AHEAD_SIZE)
149            }
150            Transform::None | Transform::Encrypted(_) => TARGET_READ_AHEAD_SIZE,
151        }
152    }
153
154    /// Returns the Merkle verification alignment in bytes for this file (`chunk_size` for
155    /// compressed files, or [`DEFAULT_VERIFICATION_ALIGNMENT`] for uncompressed/encrypted files).
156    pub fn verification_alignment(&self) -> u64 {
157        match &self.transform {
158            Transform::Compressed(info) => info.chunk_size(),
159            Transform::None | Transform::Encrypted(_) => DEFAULT_VERIFICATION_ALIGNMENT,
160        }
161    }
162
163    /// Streams and decodes the uncompressed range requested by `page_request`, applying readahead.
164    pub fn read_range(
165        self: &Arc<Self>,
166        service: &(impl BlockService + ?Sized),
167        page_request: impl PageRequest,
168    ) {
169        self.read_range_with_recording(self.read_ahead_size(), service, page_request, |_| {});
170    }
171
172    /// Streams and decodes the uncompressed range requested by `page_request`, aligning the read
173    /// window to `window_alignment` and delivering chunks at `self.verification_alignment()`.
174    ///
175    /// Unlike [`Self::read_range`] (which expands the read window to `self.read_ahead_size()`),
176    /// this allows a caller (such as a child file mapping) to bound the read window to a smaller
177    /// alignment (e.g. a single verification chunk of the parent file).
178    pub fn read_range_with_alignment(
179        self: &Arc<Self>,
180        window_alignment: u64,
181        service: &(impl BlockService + ?Sized),
182        page_request: impl PageRequest,
183    ) {
184        self.read_range_with_recording(window_alignment, service, page_request, |_| {});
185    }
186
187    fn read_range_with_recording(
188        self: &Arc<Self>,
189        window_alignment: u64,
190        service: &(impl BlockService + ?Sized),
191        mut page_request: impl PageRequest,
192        record_offset: impl Fn(u64),
193    ) {
194        let page_size = zx::system_get_page_size() as u64;
195        let original_range = page_request.range();
196        if original_range.is_empty() {
197            return;
198        }
199
200        let page_aligned_size = self.uncompressed_size.next_multiple_of(page_size);
201        if original_range.start >= page_aligned_size {
202            return;
203        }
204
205        let chunk_size = self.verification_alignment();
206        let read_range = (original_range.start / window_alignment) * window_alignment
207            ..std::cmp::min(
208                original_range.end.next_multiple_of(window_alignment),
209                page_aligned_size,
210            );
211        if page_request.prepare(read_range.clone(), chunk_size as usize).is_err() {
212            return;
213        }
214
215        for offset in (read_range.start..read_range.end).step_by(window_alignment as usize) {
216            record_offset(offset);
217        }
218
219        match &self.transform {
220            Transform::None => {
221                let mut current_offset = read_range.start;
222                let uncompressed_size = self.uncompressed_size;
223
224                read_aligned_range(&self.extents, read_range, service, move |res| {
225                    let buffer = match res {
226                        Ok(buffer) => buffer,
227                        Err(error) => {
228                            if !is_cancelled(&error) {
229                                log::error!(error:?; "Failed to read blocks for mapped file");
230                            }
231                            return ControlFlow::Break(());
232                        }
233                    };
234                    let valid_len =
235                        min(buffer.len() as u64, uncompressed_size.saturating_sub(current_offset))
236                            as usize;
237                    let dest = page_request.mut_ptr_slice().subslice_mut(0..buffer.len());
238                    let (mut head, mut tail) = dest.split_at_mut(valid_len);
239                    head.copy_from_ptr_slice(buffer.as_ptr_slice().subslice(0..valid_len));
240                    tail.fill(0);
241                    if page_request.commit(buffer.len()).is_err() {
242                        return ControlFlow::Break(());
243                    }
244                    current_offset += buffer.len() as u64;
245                    ControlFlow::Continue(())
246                });
247            }
248            Transform::Compressed(_) => {
249                let Ok((mut decompressor, aligned_range)) = StreamingDecompressor::new(
250                    FileCompression(self.clone()),
251                    self.uncompressed_size,
252                    page_request,
253                ) else {
254                    // The range must be out of range. This should be handled when `page_request`
255                    // is dropped.
256                    return;
257                };
258
259                read_aligned_range(&self.extents, aligned_range, service, move |res| {
260                    let buffer = match res {
261                        Ok(buffer) => buffer,
262                        Err(error) => {
263                            if !is_cancelled(&error) {
264                                log::error!(
265                                    error:?;
266                                    "Failed to read blocks for compressed mapped file"
267                                );
268                            }
269                            return ControlFlow::Break(());
270                        }
271                    };
272                    if decompressor.push(buffer.as_ptr_slice()).is_err() {
273                        return ControlFlow::Break(());
274                    }
275                    ControlFlow::Continue(())
276                });
277            }
278            Transform::Encrypted(cipher) => {
279                let mut current_offset = read_range.start;
280                let uncompressed_size = self.uncompressed_size;
281                let cipher = cipher.clone();
282
283                read_aligned_range(&self.extents, read_range, service, move |res| {
284                    let buffer = match res {
285                        Ok(buffer) => buffer,
286                        Err(error) => {
287                            if !is_cancelled(&error) {
288                                log::error!(
289                                    error:?;
290                                    "Failed to read blocks for encrypted mapped file"
291                                );
292                            }
293                            return ControlFlow::Break(());
294                        }
295                    };
296                    let mut dest = page_request.mut_ptr_slice().subslice_mut(0..buffer.len());
297                    // Both `buffer` and `dest` are aligned to 64 bytes.
298                    if let Err(error) = cipher.decrypt_to(
299                        0,
300                        0,
301                        0,
302                        current_offset,
303                        buffer.as_ptr_slice(),
304                        dest.reborrow(),
305                    ) {
306                        log::error!(error:?; "Failed to decrypt buffer");
307                        return ControlFlow::Break(());
308                    }
309                    let valid_len =
310                        min(buffer.len() as u64, uncompressed_size.saturating_sub(current_offset))
311                            as usize;
312                    dest.subslice_mut(valid_len..buffer.len()).fill(0);
313                    if page_request.commit(buffer.len()).is_err() {
314                        return ControlFlow::Break(());
315                    }
316                    current_offset += buffer.len() as u64;
317                    ControlFlow::Continue(())
318                });
319            }
320        }
321    }
322
323    /// Services a page request for a child file (`self`, identified by `child_key`) whose extents
324    /// map to logical byte offsets in `parent_file`.
325    ///
326    /// Expands `original_range` to `self.read_ahead_size()` and iterates over the overlapping
327    /// child extents:
328    /// - [`DeviceExtent::Sparse`]: notifies `delivery_handler` via
329    ///   [`DeliveryHandler::deliver_child_sparse`] to zero-fill the child slice.
330    /// - [`DeviceExtent::Block`]: reads the corresponding byte range of `parent_file` aligned to
331    ///   `parent_file.verification_alignment()` so the downstream verifier can verify the parent
332    ///   chunks before supplying the child VMO pages.
333    /// - [`DeviceExtent::InlineTail`]: enqueues a header-only command via
334    ///   [`DeliveryHandler::deliver_child_inline_tail`] so the verifier can copy the tail bytes
335    ///   directly from the verified parent VMO without performing additional block I/O.
336    ///
337    /// Returns `true` if at least one asynchronous block read was initiated.
338    pub fn read_child_range<D: DeliveryHandler>(
339        self: &Arc<Self>,
340        child_key: u64,
341        original_range: Range<u64>,
342        parent_file: &Arc<File>,
343        service: &(impl BlockService + ?Sized),
344        delivery_handler: &Arc<D>,
345    ) -> bool {
346        let page_size = zx::system_get_page_size() as u64;
347        if original_range.is_empty() {
348            return false;
349        }
350        let page_aligned_size = self.uncompressed_size.next_multiple_of(page_size);
351        if original_range.start >= page_aligned_size {
352            return false;
353        }
354        let read_ahead_size = self.read_ahead_size();
355        let raw_start = (original_range.start / read_ahead_size) * read_ahead_size;
356        let raw_end = min(original_range.end.next_multiple_of(read_ahead_size), page_aligned_size);
357        let child_read_range = raw_start..raw_end;
358        let parent_align = parent_file.verification_alignment();
359
360        let mut started_async_io = false;
361        for extent in self
362            .extents
363            .iter_extents(child_read_range.start)
364            .take_while(|e| e.logical_range().start < child_read_range.end)
365        {
366            let slice_start = std::cmp::max(extent.logical_range().start, child_read_range.start);
367            let slice_end = min(extent.logical_range().end, child_read_range.end);
368            if slice_start >= slice_end {
369                continue;
370            }
371            let slice_len = slice_end - slice_start;
372            match extent.device_extent() {
373                crate::DeviceExtent::Sparse => {
374                    let _ =
375                        delivery_handler.deliver_child_sparse(child_key, slice_start, slice_len);
376                }
377                crate::DeviceExtent::Block(dev_offset) => {
378                    let parent_start = dev_offset + (slice_start - extent.logical_range().start);
379                    let parent_byte_range = parent_start..(parent_start + slice_len);
380                    let child_req = delivery_handler.get_child_page_request(
381                        child_key,
382                        slice_start,
383                        parent_byte_range,
384                    );
385                    parent_file.read_range_with_alignment(parent_align, service, child_req);
386                    started_async_io = true;
387                }
388                crate::DeviceExtent::InlineTail(dev_offset) => {
389                    let tail_end_in_file = min(extent.logical_range().end, self.uncompressed_size);
390                    let tail_length = tail_end_in_file.saturating_sub(slice_start) as u32;
391                    delivery_handler.deliver_child_inline_tail(
392                        dev_offset,
393                        child_key,
394                        slice_start,
395                        tail_length,
396                    );
397                }
398            }
399        }
400        started_async_io
401    }
402}
403
404struct LoadingSlot<R> {
405    requests: Vec<R>,
406    waiters: Vec<oneshot::Sender<Arc<File>>>,
407}
408
409impl<R> Default for LoadingSlot<R> {
410    fn default() -> Self {
411        Self { requests: Vec::new(), waiters: Vec::new() }
412    }
413}
414
415enum FileEntry<R> {
416    Loading(LoadingSlot<R>),
417    Loaded(Arc<File>),
418}
419
420impl<R> Default for FileEntry<R> {
421    fn default() -> Self {
422        Self::Loading(LoadingSlot::default())
423    }
424}
425
426/// Trait for handling deliveries (page request buffers and blob registrations) from [`Files`].
427pub trait DeliveryHandler: Send + Sync + 'static {
428    type Request: PageRequest;
429
430    /// Produces a [`PageRequest`] to receive decompressed or uncompressed page data.
431    fn get_page_request(self: &Arc<Self>, key: u64, range: Range<u64>) -> Self::Request;
432
433    /// Produces a [`PageRequest`] to receive parent file data for a child file mapping.
434    ///
435    /// `parent_byte_range` is the unaligned slice within the parent file that backs the child
436    /// extent starting at page-aligned `child_target_offset` in `child_key`. The returned
437    /// [`PageRequest`] will be prepared and committed with chunks aligned to the parent file's
438    /// verification alignment.
439    fn get_child_page_request(
440        self: &Arc<Self>,
441        _child_key: u64,
442        _child_target_offset: u64,
443        _parent_byte_range: Range<u64>,
444    ) -> Self::Request {
445        unreachable!()
446    }
447
448    /// Enqueues a header-only `ChildInlineTail` command instructing the downstream verifier to
449    /// supply a single inline-tail page at `child_target_offset` in `child_key` by copying
450    /// `tail_length` bytes directly from `parent_byte_offset` in the verified parent VMO.
451    fn deliver_child_inline_tail(
452        self: &Arc<Self>,
453        _parent_byte_offset: u64,
454        _child_key: u64,
455        _child_target_offset: u64,
456        _tail_length: u32,
457    ) {
458        unreachable!()
459    }
460
461    /// Delivers a sparse (zero-filled) range of `child_length` bytes at `child_target_offset` in
462    /// `child_key`.
463    fn deliver_child_sparse(
464        &self,
465        _child_key: u64,
466        _child_target_offset: u64,
467        _child_length: u64,
468    ) -> Result<(), Error> {
469        unreachable!()
470    }
471
472    /// Registers a blob's Merkle tree leaf hashes and delivery read alignment with the
473    /// downstream verifier.
474    fn register_blob(
475        &self,
476        _key: u64,
477        _merkle_leaves: &[[u8; 32]],
478        _read_alignment: usize,
479    ) -> Result<(), Error> {
480        Ok(())
481    }
482
483    /// Unregisters a file when it is closed.
484    fn unregister_file(&self, _key: u64) {}
485}
486
487/// A no-op [`DeliveryHandler`] for sessions that do not run a kernel pager or verify blobs
488/// (such as intermediate partition mapping sessions).
489pub struct NoopDeliveryHandler;
490
491impl DeliveryHandler for NoopDeliveryHandler {
492    type Request = NullPageRequest;
493
494    fn get_page_request(self: &Arc<Self>, _key: u64, _range: Range<u64>) -> NullPageRequest {
495        NullPageRequest
496    }
497}
498
499const PAGER_SHUTDOWN_KEY: u64 = 0;
500const PAGER_WAKE_KEY: u64 = 1;
501
502/// An RAII guard for the background pager thread spawned by [`Files::spawn_pager_thread`].
503///
504/// The thread services page requests for as long as this guard is kept alive, and stops
505/// when this guard is dropped.
506#[must_use = "Dropping PagerThread immediately stops the background pager thread"]
507pub struct PagerThread<S: ?Sized, D: DeliveryHandler> {
508    files: Arc<Files<S, D>>,
509    thread: Option<std::thread::JoinHandle<()>>,
510}
511
512impl<S: ?Sized, D: DeliveryHandler> Drop for PagerThread<S, D> {
513    fn drop(&mut self) {
514        let packet = zx::Packet::from_user_packet(
515            PAGER_SHUTDOWN_KEY,
516            0,
517            zx::UserPacket::from_u8_array([0; 32]),
518        );
519        let _ = self.files.port.queue(&packet);
520        if let Some(thread) = self.thread.take() {
521            let _ = thread.join();
522        }
523    }
524}
525
526// Upper bound on the profile VMO size to prevent unbounded memory growth if a recording runs for a
527// long time, since entries are appended without deduplication.
528const MAX_PROFILE_VMO_SIZE: u64 = 64 * 1024 * 1024;
529
530struct ProfileRecording {
531    vmo: zx::Vmo,
532    vmo_offset: u64,
533    buffer: Vec<u8>,
534}
535
536impl ProfileRecording {
537    fn new() -> Result<Self, zx::Status> {
538        let vmo = zx::Vmo::create_with_opts(zx::VmoOptions::RESIZABLE, 0)?;
539        Ok(Self { vmo, vmo_offset: 0, buffer: Vec::with_capacity(BLOCK_SIZE as usize) })
540    }
541
542    fn record(&mut self, key: u64, offset: u64) {
543        const MSG_SIZE: usize = std::mem::size_of::<FileMessage>();
544        // `FileMessage::decode_from_vmo` decodes in `BLOCK_SIZE` chunks and expects messages not
545        // to cross `BLOCK_SIZE` boundaries.
546        const { assert!((BLOCK_SIZE as usize).is_multiple_of(MSG_SIZE)) };
547
548        if self.vmo_offset >= MAX_PROFILE_VMO_SIZE {
549            return;
550        }
551        let mut buf = [0u8; MSG_SIZE];
552        FileMessage { id: key, offset }.encode_to((&mut buf[..]).into());
553        self.buffer.extend_from_slice(&buf);
554        if self.buffer.len() == BLOCK_SIZE as usize
555            && let Err(error) = self.flush()
556        {
557            log::warn!(error:?; "Failed to flush profile buffer to VMO");
558        }
559    }
560
561    fn flush(&mut self) -> Result<(), zx::Status> {
562        let new_size = self.vmo_offset + BLOCK_SIZE;
563        let res = self.vmo.set_size(new_size).and_then(|()| {
564            self.vmo.write(&self.buffer, self.vmo_offset)?;
565            self.vmo_offset = new_size;
566            Ok(())
567        });
568        self.buffer.clear();
569        res
570    }
571
572    fn finish(mut self) -> Result<zx::Vmo, zx::Status> {
573        if !self.buffer.is_empty() {
574            self.flush()?;
575        }
576        Ok(self.vmo)
577    }
578}
579
580/// A thread-safe registry of active [`File`] instances indexed by their Zircon pager port key.
581pub struct Files<S: ?Sized, D: DeliveryHandler> {
582    service: Arc<S>,
583    delivery_handler: Arc<D>,
584    port: zx::Port,
585    // When `Some(parent_file)`, this registry belongs to a child mapper session whose files map
586    // logical offsets into `parent_file`.
587    parent: Option<Arc<File>>,
588    map: Mutex<HashMap<u64, FileEntry<D::Request>>>,
589    // Page requests queued while a file's metadata was still loading, paired with
590    // `(key, file, req)` to be dispatched on `PagerThread` once metadata loading completes.
591    deferred_page_requests: Mutex<Vec<(u64, Arc<File>, D::Request)>>,
592    // Active profile recording, if one is in progress.
593    recording: Mutex<Option<ProfileRecording>>,
594}
595
596impl<S: BlockService + ?Sized + 'static, D: DeliveryHandler> Files<S, D> {
597    /// Creates a new file registry with the provided block service, delivery handler, and port.
598    pub fn new(service: Arc<S>, delivery_handler: D, port: zx::Port) -> Self {
599        Self::new_with_shared_handler(service, Arc::new(delivery_handler), port, None)
600    }
601
602    /// Creates a new file registry sharing an existing `Arc<D>` delivery handler, optionally
603    /// mapped as a child session over `parent_file`.
604    pub fn new_with_shared_handler(
605        service: Arc<S>,
606        delivery_handler: Arc<D>,
607        port: zx::Port,
608        parent: Option<Arc<File>>,
609    ) -> Self {
610        Self {
611            service,
612            delivery_handler,
613            port,
614            parent,
615            map: Mutex::new(HashMap::new()),
616            deferred_page_requests: Mutex::new(Vec::new()),
617            recording: Mutex::new(None),
618        }
619    }
620
621    /// Starts recording a profile of page requests serviced by `self`.
622    ///
623    /// While active, each serviced page request records a [`FileMessage`] containing the file's
624    /// Zircon pager port `key` and the readahead-aligned uncompressed logical byte `offset` within
625    /// that file.
626    ///
627    /// Returns `Err(zx::Status::BAD_STATE)` if a recording is already active.
628    pub fn start_recording(&self) -> Result<(), zx::Status> {
629        let mut recording = self.recording.lock();
630        if recording.is_some() {
631            return Err(zx::Status::BAD_STATE);
632        }
633        *recording = Some(ProfileRecording::new()?);
634        Ok(())
635    }
636
637    pub fn is_recording(&self) -> bool {
638        self.recording.lock().is_some()
639    }
640
641    /// Stops the active profile recording and returns a VMO containing the recorded
642    /// [`FileMessage`] items.
643    ///
644    /// Returns `Err(zx::Status::BAD_STATE)` if no recording is active.
645    pub fn stop_recording(&self) -> Result<zx::Vmo, zx::Status> {
646        let recording = self.recording.lock().take().ok_or(zx::Status::BAD_STATE)?;
647        recording.finish()
648    }
649
650    /// Aborts any active profile recording.
651    pub fn abort_recording(&self) {
652        *self.recording.lock() = None;
653    }
654
655    /// Returns a reference to the [`DeliveryHandler`] backing this registry.
656    pub fn delivery_handler(&self) -> &Arc<D> {
657        &self.delivery_handler
658    }
659
660    /// Returns `true` if this registry belongs to a child session whose files map logical byte
661    /// ranges within a parent file.
662    pub fn has_parent(&self) -> bool {
663        self.parent.is_some()
664    }
665
666    /// Spawns a background thread to service pager requests for `self`.
667    pub fn spawn_pager_thread(self: &Arc<Self>) -> PagerThread<S, D> {
668        let files = self.clone();
669        let thread = std::thread::spawn(move || {
670            files.run_pager_loop();
671        });
672        PagerThread { files: self.clone(), thread: Some(thread) }
673    }
674
675    // Dispatches a page request for an already-loaded `file`. When `self.parent` is `Some`, calls
676    // `file.read_child_range` directly without allocating a root `D::Request` first.
677    fn dispatch_page_range(&self, key: u64, file: &Arc<File>, original_range: Range<u64>) {
678        if let Some(parent_file) = &self.parent {
679            let _ = file.read_child_range(
680                key,
681                original_range,
682                parent_file,
683                self.service.as_ref(),
684                &self.delivery_handler,
685            );
686        } else {
687            let req = self.delivery_handler.get_page_request(key, original_range);
688            self.dispatch_page_request(key, file, req);
689        }
690    }
691
692    // Dispatches a `D::Request` (including requests that were queued in `LoadingSlot` while
693    // metadata was still loading) to either `file.read_child_range` (for child sessions) or
694    // `file.read_range_with_recording` (for root sessions).
695    fn dispatch_page_request(&self, key: u64, file: &Arc<File>, req: D::Request) {
696        let original_range = req.range();
697        if let Some(parent_file) = &self.parent {
698            drop(req);
699            let _ = file.read_child_range(
700                key,
701                original_range,
702                parent_file,
703                self.service.as_ref(),
704                &self.delivery_handler,
705            );
706        } else {
707            file.read_range_with_recording(
708                file.read_ahead_size(),
709                self.service.as_ref(),
710                req,
711                |offset| {
712                    if let Some(recording) = self.recording.lock().as_mut() {
713                        recording.record(key, offset);
714                    }
715                },
716            );
717        }
718    }
719
720    // Runs a synchronous event loop that listens on `self.port` for pager page requests and
721    // dispatches them to `Self::handle_page_request`.
722    fn run_pager_loop(self: &Arc<Self>) {
723        while let Ok(packet) = self.port.wait(zx::MonotonicInstant::INFINITE) {
724            match packet.contents() {
725                zx::PacketContents::User(_) => match packet.key() {
726                    PAGER_SHUTDOWN_KEY => break,
727                    PAGER_WAKE_KEY => {
728                        let requests = std::mem::take(&mut *self.deferred_page_requests.lock());
729                        for (key, file, req) in requests {
730                            self.dispatch_page_request(key, &file, req);
731                        }
732                    }
733                    _ => {}
734                },
735                zx::PacketContents::Pager(pager_packet)
736                    if pager_packet.command() == ZX_PAGER_VMO_READ =>
737                {
738                    self.handle_page_request(packet.key(), pager_packet.range());
739                }
740                _ => {}
741            }
742        }
743    }
744
745    /// Returns a reference to the block service.
746    pub fn service(&self) -> &Arc<S> {
747        &self.service
748    }
749
750    /// Registers a blob's Merkle tree leaf hashes and delivery read alignment with the
751    /// downstream delivery handler.
752    pub fn register_blob(
753        &self,
754        key: u64,
755        merkle_leaves: &[[u8; 32]],
756        read_alignment: usize,
757    ) -> Result<(), Error> {
758        self.delivery_handler.register_blob(key, merkle_leaves, read_alignment)
759    }
760
761    // Handles a page request from `PagerThread`.
762    //
763    // If the file is loaded, reads the range into a newly allocated buffer immediately.
764    // If the file is currently loading or unmapped, queues the request to be fulfilled
765    // when loaded.
766    fn handle_page_request(&self, key: u64, range: Range<u64>) {
767        {
768            let map = self.map.lock();
769            if let Some(FileEntry::Loaded(file)) = map.get(&key) {
770                let file = Arc::clone(file);
771                drop(map);
772                self.dispatch_page_range(key, &file, range);
773                return;
774            }
775        }
776
777        let req = self.delivery_handler.get_page_request(key, range);
778        let mut map = self.map.lock();
779        match map.entry(key) {
780            Entry::Occupied(mut entry) => match entry.get_mut() {
781                FileEntry::Loaded(file) => {
782                    let file = Arc::clone(file);
783                    drop(map);
784                    self.dispatch_page_request(key, &file, req);
785                }
786                FileEntry::Loading(slot) => {
787                    slot.requests.push(req);
788                }
789            },
790            Entry::Vacant(entry) => {
791                // Page requests can arrive before the corresponding `Mappings` command
792                // is processed. We create a loading entry and queue the request so it can be
793                // serviced once the mapping arrives and metadata finishes loading.
794                //
795                // Potential weakness: Unknown keys are unbounded. If an invalid or bogus page
796                // request arrives for a key that is never mapped, this entry will remain in
797                // memory until the session is dropped.
798                entry.insert(FileEntry::Loading(LoadingSlot {
799                    requests: vec![req],
800                    waiters: Vec::new(),
801                }));
802            }
803        }
804    }
805
806    /// Marks `key` as currently loading metadata, preserving any page requests that arrived
807    /// prior to the mapping command.
808    pub fn begin_loading(&self, key: u64) {
809        self.map.lock().entry(key).or_default();
810    }
811
812    /// Inserts a file into the registry under `key` and dispatches any page requests that
813    /// arrived while metadata was loading.
814    fn insert(&self, key: u64, file: Arc<File>) {
815        let (reqs, waiters) = {
816            let mut map = self.map.lock();
817            let prev = map.insert(key, FileEntry::Loaded(file.clone()));
818            match prev {
819                Some(FileEntry::Loading(slot)) => (slot.requests, slot.waiters),
820                _ => (Vec::new(), Vec::new()),
821            }
822        };
823
824        for waiter in waiters {
825            let _ = waiter.send(file.clone());
826        }
827
828        if !reqs.is_empty() {
829            // `insert` is called inside a block I/O completion callback. Calling
830            // `file.read_range` here would deadlock a single-threaded block completion worker
831            // when a read requires multiple buffer allocations from a full pool: allocating the
832            // next chunk blocks the completion thread waiting for earlier chunks to complete
833            // and free their buffers, which that same thread is responsible for completing.
834            // Instead, hand `reqs` to `PagerThread` so the callback can return immediately.
835            self.deferred_page_requests
836                .lock()
837                .extend(reqs.into_iter().map(|req| (key, file.clone(), req)));
838            let packet = zx::Packet::from_user_packet(
839                PAGER_WAKE_KEY,
840                0,
841                zx::UserPacket::from_u8_array([0; 32]),
842            );
843            let _ = self.port.queue(&packet);
844        }
845    }
846
847    /// Returns the loaded [`File`] registered under `key`, if present.
848    pub fn get_file(&self, key: u64) -> Option<Arc<File>> {
849        let map = self.map.lock();
850        match map.get(&key) {
851            Some(FileEntry::Loaded(file)) => Some(file.clone()),
852            _ => None,
853        }
854    }
855
856    /// Returns a future that completes with the loaded [`File`] registered under `key`,
857    /// waiting asynchronously if the file is currently loading or hasn't arrived yet.
858    pub async fn wait_for_file(&self, key: u64) -> Result<Arc<File>, Error> {
859        let receiver = {
860            let mut map = self.map.lock();
861            match map.entry(key) {
862                Entry::Occupied(mut entry) => match entry.get_mut() {
863                    FileEntry::Loaded(file) => return Ok(file.clone()),
864                    FileEntry::Loading(slot) => {
865                        let (sender, receiver) = oneshot::channel();
866                        slot.waiters.push(sender);
867                        receiver
868                    }
869                },
870                Entry::Vacant(entry) => {
871                    let (sender, receiver) = oneshot::channel();
872                    entry.insert(FileEntry::Loading(LoadingSlot {
873                        requests: Vec::new(),
874                        waiters: vec![sender],
875                    }));
876                    receiver
877                }
878            }
879        };
880
881        receiver.await.map_err(|_| anyhow!("File loading cancelled"))
882    }
883
884    /// Removes the file registered under `key`.
885    pub fn remove(&self, key: u64) {
886        if self.parent.is_none() {
887            self.delivery_handler.unregister_file(key);
888        }
889        self.map.lock().remove(&key);
890    }
891
892    /// Returns `true` if `key` is currently in the loading state.
893    #[cfg(test)]
894    pub fn is_loading(&self, key: u64) -> bool {
895        matches!(self.map.lock().get(&key), Some(FileEntry::Loading(_)))
896    }
897
898    /// Returns `true` if `key` is currently in the loading state with at least one queued page
899    /// request.
900    #[cfg(test)]
901    pub fn has_queued_page_requests(&self, key: u64) -> bool {
902        matches!(
903            self.map.lock().get(&key),
904            Some(FileEntry::Loading(slot)) if !slot.requests.is_empty()
905        )
906    }
907
908    /// Returns `true` if `key` is currently in the loaded state.
909    #[cfg(test)]
910    pub fn is_loaded(&self, key: u64) -> bool {
911        matches!(self.map.lock().get(&key), Some(FileEntry::Loaded(_)))
912    }
913}
914
915impl<S: BlockService + ?Sized + 'static> Files<S, NoopDeliveryHandler> {
916    /// Creates a file registry without a pager for intermediate (e.g. partition) sessions.
917    pub fn new_without_pager(service: Arc<S>) -> Self {
918        Self::new(service, NoopDeliveryHandler, zx::Port::create())
919    }
920}
921
922/// The version where `BlobMetadata` was introduced in Fxfs.
923/// Eventually, we'll need to integrate Fxfs's code for upgrading data structures.
924pub(crate) const BLOB_METADATA_VERSION: u32 = 53;
925
926/// Deserializes versioned `BlobMetadata` from the raw on-disk bytes.
927fn deserialize_blob_metadata(mut bytes: &[u8]) -> Result<BlobMetadata, anyhow::Error> {
928    use bincode::Options;
929    let options = bincode::DefaultOptions::new().allow_trailing_bytes();
930
931    let version = bytes.read_u32::<LittleEndian>()?;
932    if version < BLOB_METADATA_VERSION {
933        bail!(
934            "Unsupported blob metadata version: {} (expected >= {})",
935            version,
936            BLOB_METADATA_VERSION
937        );
938    }
939
940    options
941        .deserialize::<BlobMetadata>(bytes)
942        .map_err(|e| anyhow!("Failed to deserialize BlobMetadata: {e:?}"))
943}
944
945/// Metadata decoded from on-disk storage describing a blob's uncompressed size, compression info,
946/// and Merkle leaves. Compression info will be None if blob is uncompressed.
947pub struct DecodedBlobMetadata {
948    pub uncompressed_size: u64,
949    pub compression_info: Option<CompressionInfo>,
950    pub merkle_leaves: MerkleLeaves,
951}
952
953// Returns true if `error` was caused by `zx::Status::CANCELED` (e.g. when a mapper session is
954// closed while a read is pending).
955fn is_cancelled(error: &Error) -> bool {
956    error.root_cause().downcast_ref::<zx::Status>() == Some(&zx::Status::CANCELED)
957}
958
959/// Reads blob metadata asynchronously from `metadata_extents` using `service`.
960/// Once the metadata is retrieved, deserialized, and parsed, `callback` is invoked with
961/// [`DecodedBlobMetadata`].
962///
963/// On failure or if the operation is aborted, `callback` is dropped without being invoked.
964pub fn read_blob_metadata(
965    service: &(impl BlockService + ?Sized),
966    metadata_extents: &Extents,
967    stored_data_size: u64,
968    callback: impl FnOnce(DecodedBlobMetadata) + Send + 'static,
969) {
970    let mut total_metadata_len = 0u64;
971    for extent in metadata_extents.iter_extents(0) {
972        total_metadata_len += extent.len();
973    }
974    if total_metadata_len == 0 {
975        callback(DecodedBlobMetadata {
976            uncompressed_size: stored_data_size,
977            compression_info: None,
978            merkle_leaves: MerkleLeaves::new(),
979        });
980        return;
981    }
982
983    let mut callback = Some(callback);
984    let mut metadata_bytes = Some(Vec::with_capacity(total_metadata_len as usize));
985    read_aligned_range(metadata_extents, 0..total_metadata_len, service, move |res| {
986        let buffer = match res {
987            Ok(buffer) => buffer,
988            Err(error) => {
989                if !is_cancelled(&error) {
990                    log::error!(error:?; "Failed to read metadata blocks");
991                }
992                return ControlFlow::Break(());
993            }
994        };
995        let bytes = metadata_bytes.as_mut().unwrap();
996        let copy_len = min(buffer.len(), total_metadata_len as usize - bytes.len());
997        buffer.as_ptr_slice().subslice(0..copy_len).append_to(bytes);
998        if bytes.len() < total_metadata_len as usize {
999            return ControlFlow::Continue(());
1000        }
1001
1002        let metadata_bytes = metadata_bytes.take().unwrap();
1003        let cb = callback.take().unwrap();
1004        let metadata = match deserialize_blob_metadata(&metadata_bytes) {
1005            Ok(metadata) => metadata,
1006            Err(error) => {
1007                log::error!(error:?; "Failed to deserialize BlobMetadata");
1008                return ControlFlow::Break(());
1009            }
1010        };
1011
1012        let merkle_leaves = metadata.merkle_leaves;
1013        let res = match metadata.format {
1014            BlobFormat::Uncompressed => DecodedBlobMetadata {
1015                uncompressed_size: stored_data_size,
1016                compression_info: None,
1017                merkle_leaves,
1018            },
1019            BlobFormat::ChunkedZstd { uncompressed_size, chunk_size, compressed_offsets } => {
1020                match CompressionInfo::new(
1021                    chunk_size,
1022                    stored_data_size,
1023                    &compressed_offsets,
1024                    CompressionAlgorithm::Zstd,
1025                ) {
1026                    Ok(compression_info) => DecodedBlobMetadata {
1027                        uncompressed_size,
1028                        compression_info: Some(compression_info),
1029                        merkle_leaves,
1030                    },
1031                    Err(error) => {
1032                        log::error!(error:?; "Failed to parse Zstd CompressionInfo");
1033                        return ControlFlow::Break(());
1034                    }
1035                }
1036            }
1037            BlobFormat::ChunkedLz4 { uncompressed_size, chunk_size, compressed_offsets } => {
1038                match CompressionInfo::new(
1039                    chunk_size,
1040                    stored_data_size,
1041                    &compressed_offsets,
1042                    CompressionAlgorithm::Lz4,
1043                ) {
1044                    Ok(compression_info) => DecodedBlobMetadata {
1045                        uncompressed_size,
1046                        compression_info: Some(compression_info),
1047                        merkle_leaves,
1048                    },
1049                    Err(error) => {
1050                        log::error!(error:?; "Failed to parse Lz4 CompressionInfo");
1051                        return ControlFlow::Break(());
1052                    }
1053                }
1054            }
1055        };
1056        cb(res);
1057        ControlFlow::Break(())
1058    });
1059}
1060
1061/// RAII guard that manages the lifecycle of a file transitioning from loading metadata to loaded.
1062///
1063/// When metadata is being fetched asynchronously from storage, the file entry in [`Files`]
1064/// remains in the [`FileEntry::Loading`] state, accumulating incoming page requests in its queue.
1065///
1066/// - On success: [`LoadingFileGuard::commit`] consumes the guard, stores the fully initialized
1067///   [`File`], and dispatches all queued page requests to [`PagerThread`] to be fulfilled.
1068/// - On failure or cancellation: If dropped before `commit` is called (e.g. due to storage I/O
1069///   error, corrupted metadata, or session teardown), the `Drop` implementation cleans up the
1070///   entry by removing `key` from [`Files`]. Dropping the loading slot drops all queued
1071///   [`PageRequest`] objects, which fails the pending page requests in the kernel pager.
1072struct LoadingFileGuard<S: BlockService + ?Sized + 'static, D: DeliveryHandler> {
1073    files: Option<Arc<Files<S, D>>>,
1074    key: u64,
1075}
1076
1077impl<S: BlockService + ?Sized + 'static, D: DeliveryHandler> LoadingFileGuard<S, D> {
1078    /// Commits the loaded file to the registry, transferring ownership and dispatching any queued
1079    /// page requests to [`PagerThread`] to be fulfilled.
1080    fn commit(mut self, file: Arc<File>) {
1081        self.files.take().unwrap().insert(self.key, file);
1082    }
1083}
1084
1085impl<S: BlockService + ?Sized + 'static, D: DeliveryHandler> Drop for LoadingFileGuard<S, D> {
1086    fn drop(&mut self) {
1087        if let Some(files) = self.files.take() {
1088            files.remove(self.key);
1089        }
1090    }
1091}
1092
1093/// Processes a raw mapping command (`RawMappingCommand`), decoding extent descriptors,
1094/// reading blob metadata from storage, registering Merkle leaves via `files`, and
1095/// inserting/removing the file from `files`.
1096pub fn process_mapping_command<S: BlockService + ?Sized + 'static, D: DeliveryHandler>(
1097    msg: &Message<'_, RawMappingCommand>,
1098    files: &Arc<Files<S, D>>,
1099) -> Result<(), Error> {
1100    let cmd = **msg;
1101    match MappingCommand::try_from(cmd)? {
1102        MappingCommand::Mappings {
1103            key,
1104            offset,
1105            stored_size,
1106            device_offset,
1107            metadata_count,
1108            extent_count,
1109            encrypted,
1110        } => {
1111            if encrypted && metadata_count > 0 {
1112                bail!("Encrypted files cannot have blob metadata");
1113            }
1114            let extent_bytes_len = (extent_count as usize)
1115                .checked_mul(8)
1116                .ok_or_else(|| anyhow!("Overflow calculating extent byte length"))?;
1117            let metadata_bytes_len = (metadata_count as usize)
1118                .checked_mul(8)
1119                .ok_or_else(|| anyhow!("Overflow calculating metadata extent byte length"))?;
1120            let key_bytes_len = if encrypted { ENCRYPTION_KEY_SIZE } else { 0 };
1121            let total_bytes_len = extent_bytes_len
1122                .checked_add(metadata_bytes_len)
1123                .and_then(|len| len.checked_add(key_bytes_len))
1124                .ok_or_else(|| anyhow!("Overflow calculating total extent byte length"))?;
1125            let payload_len: u32 = total_bytes_len
1126                .try_into()
1127                .map_err(|_| anyhow!("Extent byte length exceeds u32"))?;
1128
1129            let payload_bytes = msg.payload_slice(offset, payload_len);
1130            let data_bytes = payload_bytes.subslice(0..extent_bytes_len);
1131            let data_extents = Extents::from_encoded(data_bytes.iter_as::<u64>(), device_offset)
1132                .ok_or_else(|| anyhow!("Failed to decode data extents"))?;
1133
1134            if encrypted {
1135                let key_bytes = payload_bytes.subslice(extent_bytes_len..total_bytes_len);
1136                let cipher_key = UnwrappedKey::new(key_bytes.to_vec());
1137                let cipher = Arc::new(FxfsCipher::new(&cipher_key)) as Arc<dyn Cipher>;
1138                let file =
1139                    Arc::new(File::new(data_extents, stored_size, Transform::Encrypted(cipher)));
1140                files.insert(key, file);
1141                return Ok(());
1142            }
1143
1144            if files.has_parent() && metadata_count == 0 {
1145                let file = Arc::new(File::new(data_extents, stored_size, Transform::None));
1146                files.insert(key, file);
1147                return Ok(());
1148            }
1149
1150            let metadata_bytes = payload_bytes.subslice(extent_bytes_len..total_bytes_len);
1151            let metadata_extents =
1152                Extents::from_encoded(metadata_bytes.iter_as::<u64>(), device_offset)
1153                    .ok_or_else(|| anyhow!("Failed to decode metadata extents"))?;
1154
1155            files.begin_loading(key);
1156            let service = files.service().clone();
1157            let guard = LoadingFileGuard { files: Some(files.clone()), key };
1158            let files_clone = files.clone();
1159            read_blob_metadata(service.as_ref(), &metadata_extents, stored_size, move |metadata| {
1160                let file = Arc::new(File::new(
1161                    data_extents,
1162                    metadata.uncompressed_size,
1163                    metadata.compression_info.into(),
1164                ));
1165                // Child files do not have their own Merkle trees; their backing parent blob is
1166                // verified instead.
1167                if !files_clone.has_parent() {
1168                    if let Err(error) = files_clone.register_blob(
1169                        key,
1170                        &metadata.merkle_leaves,
1171                        file.verification_alignment() as usize,
1172                    ) {
1173                        log::error!(error:?; "Failed to register blob {key}");
1174                        return;
1175                    }
1176                }
1177                guard.commit(file);
1178            });
1179            Ok(())
1180        }
1181        MappingCommand::CloseBlob { key } => {
1182            files.remove(key);
1183            Ok(())
1184        }
1185    }
1186}
1187
1188#[cfg(test)]
1189mod tests {
1190    use super::*;
1191    use crate::reader::tests::FakeBlockService;
1192    use crate::reader::{MAX_READ_BUFFER_SIZE, OwnedBuffer};
1193    use crate::testing::{TestDeliveryHandler, TestVecBuffer};
1194    use crate::{BLOCK_SIZE, Extent};
1195    use anyhow::Error;
1196    use bincode::Options;
1197    use byteorder::WriteBytesExt;
1198    use delivery_blob::compression::{ChunkedArchiveOptions, CompressionAlgorithm};
1199    use fuchsia_async as fasync;
1200    use fxfs_crypto::{Cipher, FxfsCipher, UnwrappedKey};
1201    use std::sync::Arc;
1202    use storage_device::buffer_allocator::{BufferAllocator, BufferSource};
1203    use storage_ptr_slice::MutPtrByteSlice;
1204
1205    fn serialize_metadata(metadata: &BlobMetadata) -> Vec<u8> {
1206        let mut bytes = Vec::new();
1207        bytes.write_u32::<LittleEndian>(BLOB_METADATA_VERSION).unwrap();
1208        bincode::DefaultOptions::new()
1209            .allow_trailing_bytes()
1210            .serialize_into(&mut bytes, metadata)
1211            .unwrap();
1212        bytes
1213    }
1214
1215    #[test]
1216    fn test_read_range_uncompressed() {
1217        let block_count = 8;
1218        let mut expected_data = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
1219        for (i, byte) in expected_data.iter_mut().enumerate() {
1220            *byte = (i % 255) as u8;
1221        }
1222        let service = FakeBlockService::new(expected_data.clone());
1223
1224        let extents = Extents::try_new([Extent::new(0..(8 * BLOCK_SIZE), Some(0))], 0).unwrap();
1225        let file = Arc::new(File::new(extents, 8 * BLOCK_SIZE, Transform::None));
1226
1227        let (page_request, rx) = TestVecBuffer::new_with_range(0..(8 * BLOCK_SIZE));
1228        file.read_range(&service, page_request);
1229
1230        assert_eq!(rx.commits(), vec![(0, (8 * BLOCK_SIZE) as usize)]);
1231        assert_eq!(rx.output(), expected_data);
1232    }
1233
1234    #[test]
1235    fn test_read_range_compressed_zstd() {
1236        let uncompressed_size = 32768 * 2 + 1024;
1237        let mut uncompressed_data = vec![0u8; uncompressed_size];
1238        for (i, byte) in uncompressed_data.iter_mut().enumerate() {
1239            *byte = ((i * 7) % 255) as u8;
1240        }
1241
1242        let options =
1243            ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Zstd };
1244        let archive =
1245            delivery_blob::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
1246
1247        let mut compressed_offsets = vec![0];
1248        let mut compressed_data = vec![];
1249        for chunk in archive.chunks() {
1250            compressed_data.extend_from_slice(&chunk.compressed_data);
1251            compressed_offsets.push(compressed_data.len() as u64);
1252        }
1253        compressed_offsets.pop();
1254
1255        let chunk_size = archive.chunk_size();
1256        let stored_size = compressed_data.len() as u64;
1257        let stored_blocks = stored_size.div_ceil(BLOCK_SIZE);
1258        let mut device_data = vec![0u8; (stored_blocks * BLOCK_SIZE) as usize];
1259        device_data[..compressed_data.len()].copy_from_slice(&compressed_data);
1260        let service = FakeBlockService::new(device_data);
1261
1262        let extents =
1263            Extents::try_new([Extent::new(0..(stored_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1264        let compression_info = CompressionInfo::new(
1265            chunk_size as u64,
1266            stored_size,
1267            &compressed_offsets,
1268            CompressionAlgorithm::Zstd,
1269        )
1270        .unwrap();
1271        let file = Arc::new(File::new(extents, uncompressed_size as u64, compression_info.into()));
1272
1273        let dest_alloc_size = uncompressed_size.next_multiple_of(chunk_size);
1274        let (mut page_request, rx) = TestVecBuffer::new_with_range(0..(uncompressed_size as u64));
1275        page_request.data.resize(dest_alloc_size, 0);
1276        file.read_range(&service, page_request);
1277
1278        assert_eq!(
1279            rx.commits(),
1280            vec![
1281                (0, chunk_size),
1282                (chunk_size as u64, chunk_size),
1283                (chunk_size as u64 * 2, chunk_size)
1284            ]
1285        );
1286        assert_eq!(&rx.output()[..uncompressed_size], &uncompressed_data[..]);
1287    }
1288
1289    #[test]
1290    fn test_read_range_compressed_lz4_split_across_buffers() {
1291        let uncompressed_size = 32768 * 2;
1292        let mut uncompressed_data = vec![0u8; uncompressed_size];
1293        for (i, byte) in uncompressed_data.iter_mut().enumerate() {
1294            *byte = ((i * 13) % 255) as u8;
1295        }
1296
1297        let options =
1298            ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
1299        let archive =
1300            delivery_blob::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
1301
1302        let mut compressed_offsets = vec![0];
1303        let mut compressed_data = vec![];
1304        for chunk in archive.chunks() {
1305            compressed_data.extend_from_slice(&chunk.compressed_data);
1306            compressed_offsets.push(compressed_data.len() as u64);
1307        }
1308        compressed_offsets.pop();
1309
1310        let chunk_size = archive.chunk_size();
1311        let stored_size = compressed_data.len() as u64;
1312        let stored_blocks = stored_size.div_ceil(BLOCK_SIZE);
1313        let mut device_data = vec![0u8; (stored_blocks * BLOCK_SIZE) as usize];
1314        device_data[..compressed_data.len()].copy_from_slice(&compressed_data);
1315
1316        // Force a small block allocation limit (e.g. 4096 bytes) so that read_aligned_range
1317        // splits the compressed chunks across multiple consecutive OwnedBuffers!
1318        let service = FakeBlockService::new_with_cap(device_data, Some(4096));
1319
1320        let extents =
1321            Extents::try_new([Extent::new(0..(stored_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1322        let compression_info = CompressionInfo::new(
1323            chunk_size as u64,
1324            stored_size,
1325            &compressed_offsets,
1326            CompressionAlgorithm::Lz4,
1327        )
1328        .unwrap();
1329        let file = Arc::new(File::new(extents, uncompressed_size as u64, compression_info.into()));
1330
1331        let (page_request, rx) = TestVecBuffer::new_with_range(0..(uncompressed_size as u64));
1332        file.read_range(&service, page_request);
1333
1334        assert_eq!(rx.commits(), vec![(0, chunk_size), (chunk_size as u64, chunk_size)]);
1335        assert_eq!(rx.output(), uncompressed_data);
1336    }
1337
1338    #[test]
1339    fn test_read_range_invalid_range_noop() {
1340        let service = FakeBlockService::new(vec![0u8; 8192]);
1341        let extents = Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap();
1342        let file = Arc::new(File::new(extents, 8192, Transform::None));
1343
1344        let (page_request, rx) = TestVecBuffer::new_with_range(4096..4096);
1345        // start >= end should be a no-op returning Ok(())
1346        file.read_range(&service, page_request);
1347        assert_eq!(rx.commits().len(), 0);
1348    }
1349
1350    #[test]
1351    fn test_file_getters() {
1352        let extents = Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap();
1353        let uncompressed_size = 8192u64;
1354
1355        let file_uncompressed = File::new(extents, uncompressed_size, Transform::None);
1356        assert_eq!(file_uncompressed.uncompressed_size(), 8192);
1357        assert!(matches!(file_uncompressed.transform(), Transform::None));
1358        assert!(file_uncompressed.compression_info().is_none());
1359        assert!(file_uncompressed.cipher().is_none());
1360
1361        let compression_info =
1362            CompressionInfo::new(32768, 4096, &[0], CompressionAlgorithm::Zstd).unwrap();
1363        let file_compressed = File::new(
1364            Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap(),
1365            uncompressed_size,
1366            compression_info.into(),
1367        );
1368        assert!(matches!(file_compressed.transform(), Transform::Compressed(_)));
1369        assert!(file_compressed.compression_info().is_some());
1370        assert!(file_compressed.cipher().is_none());
1371
1372        let key = UnwrappedKey::new(vec![0x42u8; 32]);
1373        let cipher: Arc<dyn Cipher> = Arc::new(FxfsCipher::new(&key));
1374        let file_encrypted = File::new(
1375            Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap(),
1376            uncompressed_size,
1377            cipher.into(),
1378        );
1379        assert!(matches!(file_encrypted.transform(), Transform::Encrypted(_)));
1380        assert!(file_encrypted.compression_info().is_none());
1381        assert!(file_encrypted.cipher().is_some());
1382    }
1383
1384    #[test]
1385    fn test_read_range_block_service_error_returns_err() {
1386        struct FailingBlockService;
1387        impl BlockService for FailingBlockService {
1388            fn allocate_buffer(&self, max_len: usize) -> storage_device::buffer::OwnedBuffer {
1389                FakeBlockService::new(vec![0u8; max_len]).allocate_buffer(max_len)
1390            }
1391            fn read_blocks(
1392                &self,
1393                _device_offset: u64,
1394                _dest_buffer: storage_device::buffer::OwnedBuffer,
1395                _on_complete: Box<
1396                    dyn FnOnce(Result<storage_device::buffer::OwnedBuffer, Error>) + Send,
1397                >,
1398            ) -> Result<(), Error> {
1399                Err(anyhow::anyhow!("block read failure"))
1400            }
1401        }
1402
1403        let extents = Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap();
1404        let file = Arc::new(File::new(extents, 8192, Transform::None));
1405
1406        let (page_request, rx) = TestVecBuffer::new_with_range(0..8192);
1407
1408        file.read_range(&FailingBlockService, page_request);
1409        assert_eq!(rx.commits().len(), 0);
1410    }
1411
1412    #[test]
1413    fn test_read_range_uncompressed_multi_chunk() {
1414        let block_count = 4;
1415        let mut expected_data = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
1416        for (i, byte) in expected_data.iter_mut().enumerate() {
1417            *byte = ((i * 11) % 255) as u8;
1418        }
1419        // Force capping to 4096 bytes per buffer allocation so read_range processes
1420        // 4 separate chunks.
1421        let service = FakeBlockService::new_with_cap(expected_data.clone(), Some(4096));
1422
1423        let extents =
1424            Extents::try_new([Extent::new(0..(block_count * BLOCK_SIZE), Some(0))], 0).unwrap();
1425        let file = Arc::new(File::new(extents, block_count * BLOCK_SIZE, Transform::None));
1426
1427        let (page_request, rx) = TestVecBuffer::new_with_range(0..(block_count * BLOCK_SIZE));
1428        file.read_range(&service, page_request);
1429
1430        assert_eq!(rx.commits().len(), 4);
1431        assert_eq!(rx.output(), expected_data);
1432    }
1433
1434    #[test]
1435    fn test_read_range_uncompressed_unaligned_uncompressed_size() {
1436        let uncompressed_size = 5000u64;
1437        let mut expected_data = vec![0u8; 8192];
1438        for (i, byte) in expected_data.iter_mut().enumerate() {
1439            *byte = (i % 251) as u8;
1440        }
1441        let service = FakeBlockService::new(expected_data.clone());
1442
1443        let extents = Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap();
1444        let file = Arc::new(File::new(extents, uncompressed_size, Transform::None));
1445
1446        let (page_request, rx) = TestVecBuffer::new_with_range(0..8192);
1447        file.read_range(&service, page_request);
1448
1449        assert_eq!(rx.commits(), vec![(0, 8192)]);
1450        assert_eq!(&rx.output()[..5000], &expected_data[..5000]);
1451    }
1452
1453    #[test]
1454    fn test_read_range_uncompressed_readahead() {
1455        let total_blocks = 64; // 256 KiB
1456        let mut expected_data = vec![0u8; (total_blocks * BLOCK_SIZE) as usize];
1457        for (i, byte) in expected_data.iter_mut().enumerate() {
1458            *byte = ((i * 17) % 251) as u8;
1459        }
1460        let service = FakeBlockService::new(expected_data.clone());
1461
1462        let extents =
1463            Extents::try_new([Extent::new(0..(total_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1464        let file = Arc::new(File::new(extents, total_blocks * BLOCK_SIZE, Transform::None));
1465
1466        // Request 1 block at offset 4096. Readahead should expand to 0..128 KiB.
1467        let (page_request, rx) = TestVecBuffer::new_with_range(4096..8192);
1468        file.read_range(&service, page_request);
1469
1470        assert_eq!(rx.commits(), vec![(0, TARGET_READ_AHEAD_SIZE as usize)]);
1471        assert_eq!(
1472            &rx.output()[..TARGET_READ_AHEAD_SIZE as usize],
1473            &expected_data[..TARGET_READ_AHEAD_SIZE as usize]
1474        );
1475    }
1476
1477    #[test]
1478    fn test_read_range_uncompressed_readahead_second_window() {
1479        let total_blocks = 64; // 256 KiB
1480        let mut expected_data = vec![0u8; (total_blocks * BLOCK_SIZE) as usize];
1481        for (i, byte) in expected_data.iter_mut().enumerate() {
1482            *byte = ((i * 19) % 251) as u8;
1483        }
1484        let service = FakeBlockService::new(expected_data.clone());
1485
1486        let extents =
1487            Extents::try_new([Extent::new(0..(total_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1488        let file = Arc::new(File::new(extents, total_blocks * BLOCK_SIZE, Transform::None));
1489
1490        // Request 1 block at offset 132 KiB (135168..139264).
1491        // Readahead should expand to 128 KiB..256 KiB (131072..262144).
1492        let (page_request, rx) = TestVecBuffer::new_with_range(135168..139264);
1493        file.read_range(&service, page_request);
1494
1495        assert_eq!(rx.commits(), vec![(TARGET_READ_AHEAD_SIZE, TARGET_READ_AHEAD_SIZE as usize)]);
1496        assert_eq!(
1497            &rx.output()[..TARGET_READ_AHEAD_SIZE as usize],
1498            &expected_data[TARGET_READ_AHEAD_SIZE as usize..2 * TARGET_READ_AHEAD_SIZE as usize]
1499        );
1500    }
1501
1502    #[test]
1503    fn test_read_range_uncompressed_readahead_tail_capped() {
1504        let uncompressed_size = 140_000u64;
1505        let total_blocks = (uncompressed_size.next_multiple_of(BLOCK_SIZE) / BLOCK_SIZE) as u64;
1506        let mut expected_data = vec![0u8; (total_blocks * BLOCK_SIZE) as usize];
1507        for i in 0..uncompressed_size as usize {
1508            expected_data[i] = ((i * 23) % 251) as u8;
1509        }
1510        let service = FakeBlockService::new(expected_data.clone());
1511
1512        let extents =
1513            Extents::try_new([Extent::new(0..(total_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1514        let file = Arc::new(File::new(extents, uncompressed_size, Transform::None));
1515
1516        // Request 1 block at offset 132 KiB (135168..139264).
1517        // Readahead window starts at 128 KiB (131072) and would normally extend to 256 KiB
1518        // (262144), but should be capped at page_aligned_size (143360).
1519        let (page_request, rx) = TestVecBuffer::new_with_range(135168..139264);
1520        file.read_range(&service, page_request);
1521
1522        let expected_start = TARGET_READ_AHEAD_SIZE;
1523        let page_aligned_size = uncompressed_size.next_multiple_of(BLOCK_SIZE);
1524        let expected_len = (page_aligned_size - expected_start) as usize;
1525        assert_eq!(rx.commits(), vec![(expected_start, expected_len)]);
1526
1527        let valid_len = (uncompressed_size - expected_start) as usize;
1528        assert_eq!(
1529            &rx.output()[..valid_len],
1530            &expected_data[expected_start as usize..uncompressed_size as usize]
1531        );
1532        // The remaining tail bytes within the last page must be zero-filled.
1533        assert!(rx.output()[valid_len..expected_len].iter().all(|&b| b == 0));
1534    }
1535
1536    #[test]
1537    fn test_read_range_compressed_second_readahead_window() {
1538        let chunk_count = 8;
1539        let chunk_size = 32768usize;
1540        let uncompressed_size = chunk_count * chunk_size;
1541        let mut uncompressed_data = vec![0u8; uncompressed_size];
1542        for (i, byte) in uncompressed_data.iter_mut().enumerate() {
1543            *byte = ((i * 7) % 251) as u8;
1544        }
1545
1546        let options =
1547            ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Zstd };
1548        let archive =
1549            delivery_blob::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
1550
1551        let mut compressed_offsets = vec![0];
1552        let mut compressed_data = vec![];
1553        for chunk in archive.chunks() {
1554            compressed_data.extend_from_slice(&chunk.compressed_data);
1555            compressed_offsets.push(compressed_data.len() as u64);
1556        }
1557        compressed_offsets.pop();
1558
1559        let stored_size = compressed_data.len() as u64;
1560        let stored_blocks = stored_size.div_ceil(BLOCK_SIZE);
1561        let mut device_data = vec![0u8; (stored_blocks * BLOCK_SIZE) as usize];
1562        device_data[..compressed_data.len()].copy_from_slice(&compressed_data);
1563        let service = FakeBlockService::new(device_data);
1564
1565        let extents =
1566            Extents::try_new([Extent::new(0..(stored_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1567        let compression_info = CompressionInfo::new(
1568            chunk_size as u64,
1569            stored_size,
1570            &compressed_offsets,
1571            CompressionAlgorithm::Zstd,
1572        )
1573        .unwrap();
1574        let file = Arc::new(File::new(extents, uncompressed_size as u64, compression_info.into()));
1575
1576        // Request 1 block in the second 128 KiB readahead window (e.g. 135168..139264).
1577        // Readahead should expand to 131072..262144 (chunks 4, 5, 6, 7).
1578        let (page_request, rx) = TestVecBuffer::new_with_range(135168..139264);
1579        file.read_range(&service, page_request);
1580
1581        assert_eq!(
1582            rx.commits(),
1583            vec![
1584                (131072, chunk_size),
1585                (163840, chunk_size),
1586                (196608, chunk_size),
1587                (229376, chunk_size),
1588            ]
1589        );
1590        assert_eq!(&rx.output()[..131072], &uncompressed_data[131072..262144]);
1591    }
1592
1593    #[test]
1594    fn test_read_range_compressed_partial_final_chunk_zero_tail() {
1595        let uncompressed_size = 32768 + 1024;
1596        let mut uncompressed_data = vec![0u8; uncompressed_size];
1597        for (i, byte) in uncompressed_data.iter_mut().enumerate() {
1598            *byte = ((i * 13) % 251) as u8;
1599        }
1600
1601        let options =
1602            ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Zstd };
1603        let archive =
1604            delivery_blob::compression::ChunkedArchive::new(&uncompressed_data, options).unwrap();
1605
1606        let mut compressed_offsets = vec![0];
1607        let mut compressed_data = vec![];
1608        for chunk in archive.chunks() {
1609            compressed_data.extend_from_slice(&chunk.compressed_data);
1610            compressed_offsets.push(compressed_data.len() as u64);
1611        }
1612        compressed_offsets.pop();
1613
1614        let chunk_size = archive.chunk_size();
1615        let stored_size = compressed_data.len() as u64;
1616        let stored_blocks = stored_size.div_ceil(BLOCK_SIZE);
1617        let mut device_data = vec![0u8; (stored_blocks * BLOCK_SIZE) as usize];
1618        device_data[..compressed_data.len()].copy_from_slice(&compressed_data);
1619        let service = FakeBlockService::new(device_data);
1620
1621        let extents =
1622            Extents::try_new([Extent::new(0..(stored_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
1623        let compression_info = CompressionInfo::new(
1624            chunk_size as u64,
1625            stored_size,
1626            &compressed_offsets,
1627            CompressionAlgorithm::Zstd,
1628        )
1629        .unwrap();
1630        let file = Arc::new(File::new(extents, uncompressed_size as u64, compression_info.into()));
1631
1632        // Pre-fill destination buffer with 0xFF bytes to verify tail zeroing
1633        let (mut page_request, rx) = TestVecBuffer::new_with_range(0..(uncompressed_size as u64));
1634        page_request.data.resize(65536, 0);
1635        page_request.data.fill(0xFF);
1636        file.read_range(&service, page_request);
1637
1638        assert_eq!(rx.commits(), vec![(0, chunk_size), (chunk_size as u64, chunk_size)]);
1639        assert_eq!(&rx.output()[..uncompressed_size], &uncompressed_data[..]);
1640        assert_eq!(&rx.output()[uncompressed_size..65536], &[0u8; 31744]);
1641    }
1642
1643    #[test]
1644    fn test_files_registry() {
1645        let extents = Extents::try_new([Extent::new(0..4096, Some(0))], 0).unwrap();
1646        let file = Arc::new(File::new(extents, 4096, Transform::None));
1647        let service = Arc::new(FakeBlockService::new(vec![0u8; 4096]));
1648        let files = Files::new(
1649            service,
1650            TestDeliveryHandler(|_key, _range| TestVecBuffer::new(4096).0),
1651            zx::Port::create(),
1652        );
1653
1654        assert!(!files.is_loading(100));
1655        files.begin_loading(100);
1656        assert!(files.is_loading(100));
1657
1658        files.insert(100, file.clone());
1659        assert!(!files.is_loading(100));
1660        files.remove(100);
1661        assert!(!files.is_loading(100));
1662    }
1663
1664    #[test]
1665    fn test_files_new_without_pager() {
1666        let extents = Extents::try_new([Extent::new(0..4096, Some(0))], 0).unwrap();
1667        let file = Arc::new(File::new(extents, 4096, Transform::None));
1668        let service = Arc::new(FakeBlockService::new(vec![0u8; 4096]));
1669        let files = Files::new_without_pager(service);
1670
1671        files.insert(100, file.clone());
1672        assert_eq!(files.get_file(100).unwrap().uncompressed_size(), 4096);
1673    }
1674
1675    struct DelayedBlockService {
1676        device_data: Vec<u8>,
1677        pending: Mutex<Vec<Box<dyn FnOnce() + Send>>>,
1678        allocator: Option<Arc<BufferAllocator>>,
1679    }
1680
1681    impl DelayedBlockService {
1682        fn new(device_data: Vec<u8>) -> Arc<Self> {
1683            Arc::new(Self { device_data, pending: Mutex::new(Vec::new()), allocator: None })
1684        }
1685
1686        fn new_with_pool_capacity(device_data: Vec<u8>, pool_capacity: usize) -> Arc<Self> {
1687            Arc::new(Self {
1688                device_data,
1689                pending: Mutex::new(Vec::new()),
1690                allocator: Some(Arc::new(BufferAllocator::new(
1691                    BLOCK_SIZE as usize,
1692                    BufferSource::new(pool_capacity),
1693                ))),
1694            })
1695        }
1696
1697        fn wait_and_trigger_sync(&self) {
1698            loop {
1699                let callbacks = std::mem::take(&mut *self.pending.lock());
1700                if !callbacks.is_empty() {
1701                    for cb in callbacks {
1702                        cb();
1703                    }
1704                    break;
1705                }
1706                std::thread::sleep(std::time::Duration::from_millis(10));
1707            }
1708        }
1709    }
1710
1711    impl BlockService for DelayedBlockService {
1712        fn allocate_buffer(&self, max_len: usize) -> OwnedBuffer {
1713            if let Some(allocator) = &self.allocator {
1714                let max_len = std::cmp::min(
1715                    std::cmp::min(max_len, MAX_READ_BUFFER_SIZE),
1716                    allocator.buffer_source().size(),
1717                );
1718                allocator.allocate_buffer_sync_owned(max_len)
1719            } else {
1720                FakeBlockService::new(vec![0u8; max_len]).allocate_buffer(max_len)
1721            }
1722        }
1723
1724        fn read_blocks(
1725            &self,
1726            device_offset: u64,
1727            mut dest_buffer: OwnedBuffer,
1728            on_complete: Box<dyn FnOnce(Result<OwnedBuffer, Error>) + Send>,
1729        ) -> Result<(), Error> {
1730            let start = device_offset as usize;
1731            let end = (start + dest_buffer.len()).min(self.device_data.len());
1732            dest_buffer
1733                .as_mut_ptr_slice()
1734                .subslice_mut(0..end - start)
1735                .copy_from_slice(&self.device_data[start..end]);
1736            self.pending.lock().push(Box::new(move || {
1737                on_complete(Ok(dest_buffer));
1738            }));
1739            Ok(())
1740        }
1741    }
1742
1743    #[fuchsia::test]
1744    fn test_read_blob_metadata_callback() {
1745        let uncompressed_size = 65536u64;
1746        let chunk_size = 32768u64;
1747        let compressed_offsets = vec![0u64, 1200u64];
1748        let metadata = BlobMetadata {
1749            merkle_leaves: MerkleLeaves::new(),
1750            format: BlobFormat::ChunkedZstd {
1751                uncompressed_size,
1752                chunk_size,
1753                compressed_offsets: compressed_offsets.clone(),
1754            },
1755        };
1756        let encoded_metadata = serialize_metadata(&metadata);
1757        let mut device_data = vec![0u8; BLOCK_SIZE as usize];
1758        device_data[..encoded_metadata.len()].copy_from_slice(&encoded_metadata);
1759
1760        let service = DelayedBlockService::new(device_data);
1761        let metadata_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
1762
1763        let (tx, rx) = std::sync::mpsc::channel();
1764        read_blob_metadata(service.as_ref(), &metadata_extents, 2400, move |res| {
1765            let _ = tx.send(res);
1766        });
1767
1768        // Trigger delayed storage read completion.
1769        service.wait_and_trigger_sync();
1770
1771        let metadata = rx.recv().unwrap();
1772        assert_eq!(metadata.uncompressed_size, uncompressed_size);
1773        assert!(metadata.compression_info.is_some());
1774        assert_eq!(metadata.merkle_leaves, MerkleLeaves::new());
1775    }
1776
1777    #[fuchsia::test]
1778    fn test_read_blob_metadata_error_drops_callback() {
1779        // Corrupt CompressionInfo where compressed_offsets are invalid (descending)
1780        let metadata = BlobMetadata {
1781            merkle_leaves: MerkleLeaves::new(),
1782            format: BlobFormat::ChunkedZstd {
1783                uncompressed_size: 65536,
1784                chunk_size: 32768,
1785                compressed_offsets: vec![1000, 500],
1786            },
1787        };
1788        let encoded_metadata = serialize_metadata(&metadata);
1789        let mut device_data = vec![0u8; BLOCK_SIZE as usize];
1790        device_data[..encoded_metadata.len()].copy_from_slice(&encoded_metadata);
1791
1792        let service = DelayedBlockService::new(device_data);
1793        let metadata_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
1794
1795        let (tx, rx) = std::sync::mpsc::channel();
1796        read_blob_metadata(service.as_ref(), &metadata_extents, 1200, move |res| {
1797            let _ = tx.send(res);
1798        });
1799
1800        service.wait_and_trigger_sync();
1801
1802        // Callback should have been dropped on error without sending a message.
1803        assert!(rx.recv().is_err());
1804    }
1805
1806    #[fuchsia::test]
1807    fn test_read_blob_metadata_corrupt_bincode_drops_callback() {
1808        // Corrupt random bytes that cannot be deserialized as BlobMetadata
1809        let device_data = vec![0xFFu8; BLOCK_SIZE as usize];
1810        let service = DelayedBlockService::new(device_data);
1811        let metadata_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
1812
1813        let (tx, rx) = std::sync::mpsc::channel();
1814        read_blob_metadata(service.as_ref(), &metadata_extents, 1200, move |res| {
1815            let _ = tx.send(res);
1816        });
1817
1818        service.wait_and_trigger_sync();
1819
1820        // Callback should have been dropped on error without sending a message.
1821        assert!(rx.recv().is_err());
1822    }
1823
1824    #[fuchsia::test]
1825    fn test_process_mapping_command_error_cleans_up_blob() {
1826        let metadata = BlobMetadata {
1827            merkle_leaves: MerkleLeaves::new(),
1828            format: BlobFormat::ChunkedZstd {
1829                uncompressed_size: 65536,
1830                chunk_size: 32768,
1831                compressed_offsets: vec![1000, 500],
1832            },
1833        };
1834        let encoded_metadata = serialize_metadata(&metadata);
1835        let mut device_data = vec![0u8; (2 * BLOCK_SIZE) as usize];
1836        device_data[BLOCK_SIZE as usize..BLOCK_SIZE as usize + encoded_metadata.len()]
1837            .copy_from_slice(&encoded_metadata);
1838
1839        let service = DelayedBlockService::new(device_data);
1840        let files = Arc::new(Files::new(
1841            service.clone(),
1842            TestDeliveryHandler(|_k, _r| TestVecBuffer::new(4096).0),
1843            zx::Port::create(),
1844        ));
1845
1846        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
1847        let meta_extents =
1848            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(BLOCK_SIZE))], 0).unwrap();
1849        let mut payload_bytes = Vec::new();
1850        for w in
1851            Extents::encode_extents(&data_extents).chain(Extents::encode_extents(&meta_extents))
1852        {
1853            payload_bytes.extend_from_slice(&w.to_le_bytes());
1854        }
1855
1856        let vmo = zx::Vmo::create(65536).unwrap();
1857        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
1858            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
1859            1024,
1860            16,
1861        )
1862        .unwrap();
1863        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
1864        payload_buf.data().copy_from_slice(&payload_bytes);
1865        let cmd = crate::RawMappingCommand {
1866            opcode: crate::MAPPINGS_COMMAND,
1867            offset: payload_buf.offset(),
1868            key: 99,
1869            stored_size: 4096,
1870            device_offset: 0,
1871            metadata_count: 1,
1872            extent_count: 1,
1873        };
1874        payload_buf.commit(cmd).unwrap();
1875        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
1876
1877        let msg = receiver.peek().unwrap();
1878        process_mapping_command(&msg, &files).unwrap();
1879
1880        // File is initially in Loading state.
1881        assert!(files.is_loading(99));
1882
1883        // Complete metadata read (which will fail due to corrupt CompressionInfo).
1884        service.wait_and_trigger_sync();
1885
1886        // Files map should have cleaned up the failed entry on drop.
1887        assert!(!files.is_loading(99));
1888    }
1889
1890    #[fuchsia::test]
1891    fn test_process_mapping_command_registers_blob_merkle_leaves() {
1892        let leaf1 = [1u8; 32];
1893        let leaf2 = [2u8; 32];
1894
1895        // Prepare serialized BlobMetadata with Merkle leaves on a mock block device.
1896        let metadata =
1897            BlobMetadata { merkle_leaves: vec![leaf1, leaf2], format: BlobFormat::Uncompressed };
1898        let encoded_metadata = serialize_metadata(&metadata);
1899        let mut device_data = vec![0u8; (2 * BLOCK_SIZE) as usize];
1900        device_data[BLOCK_SIZE as usize..BLOCK_SIZE as usize + encoded_metadata.len()]
1901            .copy_from_slice(&encoded_metadata);
1902
1903        // Use DelayedBlockService to hold back the block read completion for the blob's metadata.
1904        let service = DelayedBlockService::new(device_data);
1905        let registered = Arc::new(std::sync::Mutex::new(None));
1906        let registered_clone = registered.clone();
1907        struct TestRegisterHandler(Arc<std::sync::Mutex<Option<(u64, Vec<[u8; 32]>, usize)>>>);
1908        impl DeliveryHandler for TestRegisterHandler {
1909            type Request = TestVecBuffer;
1910            fn get_page_request(self: &Arc<Self>, _key: u64, _range: Range<u64>) -> Self::Request {
1911                TestVecBuffer::new(4096).0
1912            }
1913            fn register_blob(
1914                &self,
1915                key: u64,
1916                leaves: &[[u8; 32]],
1917                read_alignment: usize,
1918            ) -> Result<(), Error> {
1919                *self.0.lock().unwrap() = Some((key, leaves.to_vec(), read_alignment));
1920                Ok(())
1921            }
1922        }
1923        let files = Arc::new(Files::new(
1924            service.clone(),
1925            TestRegisterHandler(registered_clone),
1926            zx::Port::create(),
1927        ));
1928
1929        // Encode extent descriptors for data (block 0) and metadata (block 1).
1930        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
1931        let meta_extents =
1932            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(BLOCK_SIZE))], 0).unwrap();
1933        let data_extent_words = Extents::encode_extents(&data_extents);
1934        let meta_extent_words = Extents::encode_extents(&meta_extents);
1935        let mut payload_bytes = Vec::new();
1936        for w in data_extent_words.chain(meta_extent_words) {
1937            payload_bytes.extend_from_slice(&w.to_le_bytes());
1938        }
1939
1940        // Inject the Mappings command and extent payload into the VMO FIFO.
1941        let vmo = zx::Vmo::create(65536).unwrap();
1942        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
1943            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
1944            1024,
1945            16,
1946        )
1947        .unwrap();
1948        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
1949        payload_buf.data().copy_from_slice(&payload_bytes);
1950        let cmd = crate::RawMappingCommand {
1951            opcode: crate::MAPPINGS_COMMAND,
1952            offset: payload_buf.offset(),
1953            key: 42,
1954            stored_size: 4096,
1955            device_offset: 0,
1956            metadata_count: 1,
1957            extent_count: 1,
1958        };
1959        payload_buf.commit(cmd).unwrap();
1960        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
1961
1962        // Process the mapping command.
1963        let msg = receiver.peek().unwrap();
1964        process_mapping_command(&msg, &files).unwrap();
1965
1966        // While the block read for metadata is pending completion, the file is in the "loading"
1967        // state and leaves are not yet registered.
1968        assert!(files.is_loading(42));
1969        assert!(registered.lock().unwrap().is_none());
1970
1971        // Trigger completion of the pending block read.
1972        service.wait_and_trigger_sync();
1973
1974        // Once the block read completes and metadata is decoded, verify that:
1975        // - The file transitioned out of "loading" and is committed into `Files`.
1976        // - The `register_blob` callback was invoked with the expected key, Merkle leaves, and
1977        //   read_alignment.
1978        assert!(!files.is_loading(42));
1979        assert!(files.get_file(42).is_some());
1980        let (reg_key, reg_leaves, reg_read_alignment) =
1981            registered.lock().unwrap().take().expect("registered callback called");
1982        assert_eq!(reg_key, 42);
1983        assert_eq!(reg_leaves, vec![leaf1, leaf2]);
1984        assert_eq!(reg_read_alignment, DEFAULT_VERIFICATION_ALIGNMENT as usize);
1985    }
1986
1987    #[fuchsia::test]
1988    fn test_loading_page_request_buffer_dropped_on_failure() {
1989        use delivery_blob::compression::{ChunkedArchiveError, DataBuffer};
1990        use std::sync::atomic::{AtomicBool, Ordering};
1991        use storage_ptr_slice::MutPtrByteSlice;
1992
1993        struct DroppingBuffer {
1994            data: Vec<u8>,
1995            range: Range<u64>,
1996            dropped: Arc<AtomicBool>,
1997        }
1998        impl Drop for DroppingBuffer {
1999            fn drop(&mut self) {
2000                self.dropped.store(true, Ordering::Relaxed);
2001            }
2002        }
2003        impl DataBuffer for DroppingBuffer {
2004            fn range(&self) -> Range<u64> {
2005                self.range.clone()
2006            }
2007            fn mut_ptr_slice(&mut self) -> MutPtrByteSlice<'_> {
2008                MutPtrByteSlice::from(&mut self.data[..])
2009            }
2010            fn commit(&mut self, _size: usize) -> Result<(), ChunkedArchiveError> {
2011                Ok(())
2012            }
2013        }
2014        impl PageRequest for DroppingBuffer {
2015            fn prepare(
2016                &mut self,
2017                _range: Range<u64>,
2018                _read_alignment: usize,
2019            ) -> Result<(), ChunkedArchiveError> {
2020                Ok(())
2021            }
2022        }
2023
2024        let metadata = BlobMetadata {
2025            merkle_leaves: MerkleLeaves::new(),
2026            format: BlobFormat::ChunkedZstd {
2027                uncompressed_size: 65536,
2028                chunk_size: 32768,
2029                compressed_offsets: vec![1000, 500],
2030            },
2031        };
2032        let encoded_metadata = serialize_metadata(&metadata);
2033        let mut device_data = vec![0u8; (2 * BLOCK_SIZE) as usize];
2034        device_data[BLOCK_SIZE as usize..BLOCK_SIZE as usize + encoded_metadata.len()]
2035            .copy_from_slice(&encoded_metadata);
2036
2037        let service = DelayedBlockService::new(device_data);
2038        let dropped = Arc::new(AtomicBool::new(false));
2039        let dropped_clone = dropped.clone();
2040        let files = Arc::new(Files::new(
2041            service.clone(),
2042            TestDeliveryHandler(move |_k, r| DroppingBuffer {
2043                data: vec![0u8; 4096],
2044                range: r,
2045                dropped: dropped_clone.clone(),
2046            }),
2047            zx::Port::create(),
2048        ));
2049
2050        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
2051        let meta_extents =
2052            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(BLOCK_SIZE))], 0).unwrap();
2053        let mut payload_bytes = Vec::new();
2054        for w in
2055            Extents::encode_extents(&data_extents).chain(Extents::encode_extents(&meta_extents))
2056        {
2057            payload_bytes.extend_from_slice(&w.to_le_bytes());
2058        }
2059
2060        let vmo = zx::Vmo::create(65536).unwrap();
2061        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
2062            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2063            1024,
2064            16,
2065        )
2066        .unwrap();
2067        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2068        payload_buf.data().copy_from_slice(&payload_bytes);
2069        let cmd = crate::RawMappingCommand {
2070            opcode: crate::MAPPINGS_COMMAND,
2071            offset: payload_buf.offset(),
2072            key: 99,
2073            stored_size: 4096,
2074            device_offset: 0,
2075            metadata_count: 1,
2076            extent_count: 1,
2077        };
2078        payload_buf.commit(cmd).unwrap();
2079        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2080
2081        let msg = receiver.peek().unwrap();
2082        process_mapping_command(&msg, &files).unwrap();
2083
2084        assert!(files.is_loading(99));
2085
2086        // Pager request arrives while loading. Buffer should be created and held.
2087        files.handle_page_request(99, 0..4096);
2088        assert!(!dropped.load(Ordering::Relaxed));
2089
2090        // Complete metadata read (which will fail due to corrupt CompressionInfo).
2091        service.wait_and_trigger_sync();
2092
2093        // Dropping guard cleans up LoadingSlot, which drops the queued buffer.
2094        assert!(dropped.load(Ordering::Relaxed));
2095        assert!(!files.is_loading(99));
2096    }
2097
2098    #[fuchsia::test]
2099    async fn test_pager_concurrent_request_during_metadata_read() {
2100        use vmo_fifo::SyncSender;
2101        use zx::{Pager, PagerOptions, Port, Rights, Vmo, VmoOptions};
2102
2103        let metadata =
2104            BlobMetadata { merkle_leaves: MerkleLeaves::new(), format: BlobFormat::Uncompressed };
2105        let encoded_metadata = serialize_metadata(&metadata);
2106        let mut device_data = vec![0u8; (2 * BLOCK_SIZE) as usize];
2107        // Metadata stored at physical block 1
2108        device_data[BLOCK_SIZE as usize..BLOCK_SIZE as usize + encoded_metadata.len()]
2109            .copy_from_slice(&encoded_metadata);
2110        // Data stored at physical block 0
2111        device_data[..4].copy_from_slice(&[10, 20, 30, 40]);
2112
2113        let service = DelayedBlockService::new(device_data);
2114        let port = Port::create();
2115
2116        let (page_request, rx) = TestVecBuffer::new(BLOCK_SIZE as usize);
2117        let page_request_holder = Arc::new(Mutex::new(Some(page_request)));
2118        let page_request_clone = page_request_holder.clone();
2119
2120        let files = Arc::new(Files::new(
2121            service.clone(),
2122            TestDeliveryHandler(move |_key, _range| page_request_clone.lock().take().unwrap()),
2123            port.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
2124        ));
2125
2126        let _pager_thread = files.spawn_pager_thread();
2127
2128        let pager = Pager::create(PagerOptions::empty()).unwrap();
2129        let vmo_blob = pager.create_vmo(VmoOptions::empty(), &port, 42, BLOCK_SIZE).unwrap();
2130        let vmo_blob_clone = vmo_blob.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
2131
2132        // Encode mapping command with 1 data extent and 1 metadata extent
2133        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
2134        let meta_extents =
2135            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(BLOCK_SIZE))], 0).unwrap();
2136        let mut payload_bytes = Vec::new();
2137        for w in
2138            Extents::encode_extents(&data_extents).chain(Extents::encode_extents(&meta_extents))
2139        {
2140            payload_bytes.extend_from_slice(&w.to_le_bytes());
2141        }
2142
2143        let cmd = crate::RawMappingCommand {
2144            opcode: crate::MAPPINGS_COMMAND,
2145            offset: 0,
2146            key: 42,
2147            stored_size: BLOCK_SIZE,
2148            device_offset: 0,
2149            metadata_count: 1,
2150            extent_count: 1,
2151        };
2152
2153        let vmo = Vmo::create(65536).unwrap();
2154        let mut sender = SyncSender::<crate::RawMappingCommand>::new(
2155            vmo.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
2156            1024,
2157            16,
2158        )
2159        .unwrap();
2160        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2161        payload_buf.data().copy_from_slice(&payload_bytes);
2162        payload_buf.commit(cmd).unwrap();
2163        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2164
2165        std::thread::scope(|s| {
2166            let msg = receiver.peek().unwrap();
2167            let files_for_process = files.clone();
2168            s.spawn(move || {
2169                process_mapping_command(&msg, &files_for_process).unwrap();
2170            });
2171
2172            // Wait until metadata block read has been submitted to service
2173            // (file is in Loading state).
2174            while service.pending.lock().is_empty() {
2175                std::thread::sleep(std::time::Duration::from_millis(5));
2176            }
2177
2178            // Trigger page fault from a detached background thread while file is loading metadata.
2179            std::thread::spawn(move || {
2180                let mut b = [0u8; 1];
2181                let _ = vmo_blob_clone.read(&mut b, 0);
2182            });
2183
2184            // Wait until the page request arrives and is queued in the loading entry before
2185            // completing the metadata read.
2186            while !files.has_queued_page_requests(42) {
2187                std::thread::sleep(std::time::Duration::from_millis(5));
2188            }
2189
2190            // Complete metadata block read; this will insert the file and immediately
2191            // drain queued page requests.
2192            service.wait_and_trigger_sync();
2193        });
2194
2195        // Complete data block read requested by pager thread when draining queued request.
2196        service.wait_and_trigger_sync();
2197
2198        // Check if page request made during metadata read was serviced!
2199        assert!(
2200            !rx.wait_for_commits(1).is_empty(),
2201            "Page request made during metadata read was dropped!"
2202        );
2203    }
2204
2205    #[fuchsia::test]
2206    async fn test_pager_request_arrives_before_mapping_command() {
2207        use vmo_fifo::SyncSender;
2208        use zx::{Pager, PagerOptions, Port, Rights, Vmo, VmoOptions};
2209
2210        let metadata =
2211            BlobMetadata { merkle_leaves: MerkleLeaves::new(), format: BlobFormat::Uncompressed };
2212        let encoded_metadata = serialize_metadata(&metadata);
2213        let mut device_data = vec![0u8; (2 * BLOCK_SIZE) as usize];
2214        // Metadata stored at physical block 1
2215        device_data[BLOCK_SIZE as usize..BLOCK_SIZE as usize + encoded_metadata.len()]
2216            .copy_from_slice(&encoded_metadata);
2217        // Data stored at physical block 0
2218        device_data[..4].copy_from_slice(&[10, 20, 30, 40]);
2219
2220        let service = DelayedBlockService::new(device_data);
2221        let port = Port::create();
2222
2223        let (page_request, rx) = TestVecBuffer::new(BLOCK_SIZE as usize);
2224        let page_request_holder = Arc::new(Mutex::new(Some(page_request)));
2225        let page_request_clone = page_request_holder.clone();
2226
2227        let files = Arc::new(Files::new(
2228            service.clone(),
2229            TestDeliveryHandler(move |_key, _range| page_request_clone.lock().take().unwrap()),
2230            port.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
2231        ));
2232
2233        let _pager_thread = files.spawn_pager_thread();
2234
2235        let pager = Pager::create(PagerOptions::empty()).unwrap();
2236        let vmo_blob = pager.create_vmo(VmoOptions::empty(), &port, 42, BLOCK_SIZE).unwrap();
2237        let vmo_blob_clone = vmo_blob.duplicate_handle(Rights::SAME_RIGHTS).unwrap();
2238
2239        // Encode mapping command with 1 data extent and 1 metadata extent
2240        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
2241        let meta_extents =
2242            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(BLOCK_SIZE))], 0).unwrap();
2243        let mut payload_bytes = Vec::new();
2244        for w in
2245            Extents::encode_extents(&data_extents).chain(Extents::encode_extents(&meta_extents))
2246        {
2247            payload_bytes.extend_from_slice(&w.to_le_bytes());
2248        }
2249
2250        let cmd = crate::RawMappingCommand {
2251            opcode: crate::MAPPINGS_COMMAND,
2252            offset: 0,
2253            key: 42,
2254            stored_size: BLOCK_SIZE,
2255            device_offset: 0,
2256            metadata_count: 1,
2257            extent_count: 1,
2258        };
2259
2260        let vmo = Vmo::create(65536).unwrap();
2261        let mut sender = SyncSender::<crate::RawMappingCommand>::new(
2262            vmo.duplicate_handle(Rights::SAME_RIGHTS).unwrap(),
2263            1024,
2264            16,
2265        )
2266        .unwrap();
2267        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2268        payload_buf.data().copy_from_slice(&payload_bytes);
2269        payload_buf.commit(cmd).unwrap();
2270        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2271
2272        // Trigger page fault from a background thread BEFORE the mapping command is processed.
2273        std::thread::spawn(move || {
2274            let mut b = [0u8; 1];
2275            let _ = vmo_blob_clone.read(&mut b, 0);
2276        });
2277
2278        // Wait until the page request arrives and is queued as pending mapping in `files`.
2279        while !files.has_queued_page_requests(42) {
2280            std::thread::sleep(std::time::Duration::from_millis(5));
2281        }
2282
2283        std::thread::scope(|s| {
2284            let msg = receiver.peek().unwrap();
2285            let files_for_process = files.clone();
2286            s.spawn(move || {
2287                process_mapping_command(&msg, &files_for_process).unwrap();
2288            });
2289
2290            // Trigger metadata read completion.
2291            service.wait_and_trigger_sync();
2292        });
2293
2294        // Complete data block read requested by pager thread when draining queued request.
2295        service.wait_and_trigger_sync();
2296
2297        // Check if page request made before mapping command was serviced!
2298        assert!(
2299            !rx.wait_for_commits(1).is_empty(),
2300            "Page request made before mapping command was dropped!"
2301        );
2302    }
2303
2304    #[fuchsia::test]
2305    fn test_process_mapping_command_close_blob() {
2306        let service = Arc::new(FakeBlockService::new(vec![0u8; 8192]));
2307        let files = Arc::new(Files::new(
2308            service,
2309            TestDeliveryHandler(|_k, _r| TestVecBuffer::new(4096).0),
2310            zx::Port::create(),
2311        ));
2312
2313        let vmo = zx::Vmo::create(65536).unwrap();
2314        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
2315            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2316            1024,
2317            16,
2318        )
2319        .unwrap();
2320
2321        // 1. Send Mappings command with 0 metadata extents (uncompressed file, loads immediately).
2322        let data_extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
2323        let mut payload_bytes = Vec::new();
2324        for w in Extents::encode_extents(&data_extents) {
2325            payload_bytes.extend_from_slice(&w.to_le_bytes());
2326        }
2327        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2328        payload_buf.data().copy_from_slice(&payload_bytes);
2329        let cmd = crate::RawMappingCommand {
2330            opcode: crate::MAPPINGS_COMMAND,
2331            offset: payload_buf.offset(),
2332            key: 123,
2333            stored_size: 4096,
2334            device_offset: 0,
2335            metadata_count: 0,
2336            extent_count: 1,
2337        };
2338        payload_buf.commit(cmd).unwrap();
2339
2340        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2341        let msg = receiver.peek().unwrap();
2342        process_mapping_command(&msg, &files).unwrap();
2343        msg.pop().unwrap();
2344
2345        assert!(files.is_loaded(123));
2346
2347        // 2. Send CloseBlob command.
2348        sender
2349            .push(crate::RawMappingCommand {
2350                opcode: crate::CLOSE_BLOB_COMMAND,
2351                offset: 0,
2352                key: 123,
2353                stored_size: 0,
2354                device_offset: 0,
2355                metadata_count: 0,
2356                extent_count: 0,
2357            })
2358            .unwrap();
2359
2360        let msg = receiver.peek().unwrap();
2361        process_mapping_command(&msg, &files).unwrap();
2362        msg.pop().unwrap();
2363
2364        assert!(!files.is_loaded(123));
2365    }
2366
2367    #[test]
2368    fn test_read_ahead_size_for_chunk_size() {
2369        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 32 * 1024), 32 * 1024);
2370        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 32 * 1024), 48 * 1024);
2371        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 32 * 1024), 64 * 1024);
2372
2373        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 64 * 1024), 64 * 1024);
2374        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 64 * 1024), 48 * 1024);
2375        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 64 * 1024), 64 * 1024);
2376        assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 64 * 1024), 96 * 1024);
2377
2378        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 128 * 1024), 128 * 1024);
2379        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 128 * 1024), 96 * 1024);
2380        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 128 * 1024), 128 * 1024);
2381        assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 128 * 1024), 96 * 1024);
2382    }
2383
2384    #[test]
2385    fn test_deserialize_blob_metadata() {
2386        let metadata =
2387            BlobMetadata { merkle_leaves: MerkleLeaves::new(), format: BlobFormat::Uncompressed };
2388
2389        // Valid metadata with version 53.
2390        let bytes = serialize_metadata(&metadata);
2391        let deserialized = deserialize_blob_metadata(&bytes).unwrap();
2392        assert_eq!(deserialized, metadata);
2393
2394        // Buffer too short (< 4 bytes).
2395        assert!(deserialize_blob_metadata(&[1, 2, 3]).is_err());
2396
2397        // Unsupported version (< 53).
2398        let mut old_version_bytes = bytes.clone();
2399        (&mut old_version_bytes[..4]).write_u32::<LittleEndian>(52).unwrap();
2400        assert!(deserialize_blob_metadata(&old_version_bytes).is_err());
2401    }
2402
2403    #[fuchsia::test]
2404    async fn test_wait_for_file_before_and_after_insert() {
2405        let service = Arc::new(FakeBlockService::new(vec![]));
2406        let files = Arc::new(Files::new_without_pager(service));
2407
2408        let extents = Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(0))], 0).unwrap();
2409        let file = Arc::new(File::new(extents, BLOCK_SIZE, Transform::None));
2410
2411        // Wait before insert.
2412        let files_clone = files.clone();
2413        let file_clone = file.clone();
2414        let wait_task =
2415            fasync::Task::spawn(async move { files_clone.wait_for_file(42).await.unwrap() });
2416
2417        // Insert the file.
2418        files.insert(42, file_clone);
2419        let loaded_file = wait_task.await;
2420        assert_eq!(loaded_file.uncompressed_size(), BLOCK_SIZE);
2421
2422        // Wait after insert.
2423        let loaded_file2 = files.wait_for_file(42).await.unwrap();
2424        assert_eq!(loaded_file2.uncompressed_size(), BLOCK_SIZE);
2425    }
2426
2427    #[test]
2428    fn test_read_range_encrypted() {
2429        let block_count = 8;
2430        let mut expected_data = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
2431        for (i, byte) in expected_data.iter_mut().enumerate() {
2432            *byte = (i % 255) as u8;
2433        }
2434
2435        let key = UnwrappedKey::new(vec![0x42u8; 32]);
2436        let cipher: Arc<dyn Cipher> = Arc::new(FxfsCipher::new(&key));
2437
2438        let mut encrypted_data = expected_data.clone();
2439        cipher.encrypt(0, 0, 0, 0, MutPtrByteSlice::from(&mut encrypted_data[..])).unwrap();
2440        let service = FakeBlockService::new(encrypted_data);
2441
2442        let extents = Extents::try_new([Extent::new(0..(8 * BLOCK_SIZE), Some(0))], 0).unwrap();
2443        let file = Arc::new(File::new(extents, 8 * BLOCK_SIZE, cipher.into()));
2444
2445        let (page_request, rx) = TestVecBuffer::new_with_range(0..(8 * BLOCK_SIZE));
2446        file.read_range(&service, page_request);
2447
2448        assert_eq!(rx.commits(), vec![(0, (8 * BLOCK_SIZE) as usize)]);
2449        assert_eq!(rx.output(), expected_data);
2450    }
2451
2452    #[test]
2453    fn test_read_range_encrypted_with_offset() {
2454        let total_blocks = 64; // 256 KiB
2455        let mut expected_data = vec![0u8; (total_blocks * BLOCK_SIZE) as usize];
2456        for (i, byte) in expected_data.iter_mut().enumerate() {
2457            *byte = ((i * 17) % 255) as u8;
2458        }
2459
2460        let key = UnwrappedKey::new(vec![0x5au8; 32]);
2461        let cipher: Arc<dyn Cipher> = Arc::new(FxfsCipher::new(&key));
2462
2463        let mut encrypted_data = expected_data.clone();
2464        cipher.encrypt(0, 0, 0, 0, MutPtrByteSlice::from(&mut encrypted_data[..])).unwrap();
2465        let service = FakeBlockService::new(encrypted_data);
2466
2467        let extents =
2468            Extents::try_new([Extent::new(0..(total_blocks * BLOCK_SIZE), Some(0))], 0).unwrap();
2469        let file = Arc::new(File::new(extents, total_blocks * BLOCK_SIZE, cipher.into()));
2470
2471        // Request 1 block at offset 132 KiB (135168..139264).
2472        // Readahead window expands to 128 KiB..256 KiB (131072..262144), ensuring that
2473        // current_offset starts at a non-zero offset (128 KiB) with correct sector tweak.
2474        let (page_request, rx) = TestVecBuffer::new_with_range(135168..139264);
2475        file.read_range(&service, page_request);
2476
2477        assert_eq!(rx.commits(), vec![(TARGET_READ_AHEAD_SIZE, TARGET_READ_AHEAD_SIZE as usize)]);
2478        assert_eq!(
2479            &rx.output()[..TARGET_READ_AHEAD_SIZE as usize],
2480            &expected_data[TARGET_READ_AHEAD_SIZE as usize..2 * TARGET_READ_AHEAD_SIZE as usize]
2481        );
2482    }
2483
2484    #[test]
2485    fn test_read_range_encrypted_multi_chunk() {
2486        let block_count = 4;
2487        let mut expected_data = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
2488        for (i, byte) in expected_data.iter_mut().enumerate() {
2489            *byte = ((i * 31) % 255) as u8;
2490        }
2491
2492        let key = UnwrappedKey::new(vec![0x7fu8; 32]);
2493        let cipher: Arc<dyn Cipher> = Arc::new(FxfsCipher::new(&key));
2494
2495        let mut encrypted_data = expected_data.clone();
2496        cipher.encrypt(0, 0, 0, 0, MutPtrByteSlice::from(&mut encrypted_data[..])).unwrap();
2497        // Capping to BLOCK_SIZE forces read_aligned_range to issue separate buffer reads for each chunk.
2498        let service = FakeBlockService::new_with_cap(encrypted_data, Some(BLOCK_SIZE as usize));
2499
2500        let extents =
2501            Extents::try_new([Extent::new(0..(block_count * BLOCK_SIZE), Some(0))], 0).unwrap();
2502        let file = Arc::new(File::new(extents, block_count * BLOCK_SIZE, cipher.into()));
2503
2504        let (page_request, rx) = TestVecBuffer::new_with_range(0..(block_count * BLOCK_SIZE));
2505        file.read_range(&service, page_request);
2506
2507        assert_eq!(rx.commits().len(), 4);
2508        assert_eq!(rx.output(), expected_data);
2509    }
2510
2511    #[test]
2512    fn test_read_range_decryption_error() {
2513        #[derive(Debug)]
2514        struct FailingCipher(FxfsCipher);
2515        impl Cipher for FailingCipher {
2516            fn encrypt(
2517                &self,
2518                ino: u64,
2519                attr: u64,
2520                dev: u64,
2521                file: u64,
2522                buf: MutPtrByteSlice<'_>,
2523            ) -> Result<(), Error> {
2524                self.0.encrypt(ino, attr, dev, file, buf)
2525            }
2526            fn decrypt(
2527                &self,
2528                _ino: u64,
2529                _attribute_id: u64,
2530                _device_offset: u64,
2531                _file_offset: u64,
2532                _buffer: MutPtrByteSlice<'_>,
2533            ) -> Result<(), Error> {
2534                bail!("decrypt failed")
2535            }
2536            fn encrypt_filename(&self, ino: u64, name: &mut Vec<u8>) -> Result<(), Error> {
2537                self.0.encrypt_filename(ino, name)
2538            }
2539            fn decrypt_filename(&self, ino: u64, name: &mut Vec<u8>) -> Result<(), Error> {
2540                self.0.decrypt_filename(ino, name)
2541            }
2542            fn hash_code(&self, name: &[u8], filename: &str) -> Option<u32> {
2543                self.0.hash_code(name, filename)
2544            }
2545            fn hash_code_casefold(&self, filename: &str) -> u32 {
2546                self.0.hash_code_casefold(filename)
2547            }
2548            fn supports_inline_encryption(&self) -> bool {
2549                self.0.supports_inline_encryption()
2550            }
2551            fn crypt_ctx(&self, ino: u64, attr: u64, offset: u64) -> Option<(u64, u8)> {
2552                self.0.crypt_ctx(ino, attr, offset)
2553            }
2554        }
2555
2556        let key = UnwrappedKey::new(vec![0x42u8; 32]);
2557        let failing_cipher: Arc<dyn Cipher> = Arc::new(FailingCipher(FxfsCipher::new(&key)));
2558        let service = FakeBlockService::new(vec![0u8; 8192]);
2559        let extents = Extents::try_new([Extent::new(0..8192, Some(0))], 0).unwrap();
2560        let file = Arc::new(File::new(extents, 8192, failing_cipher.into()));
2561
2562        let (page_request, rx) = TestVecBuffer::new_with_range(0..8192);
2563        file.read_range(&service, page_request);
2564        assert_eq!(rx.commits().len(), 0);
2565    }
2566
2567    #[fuchsia::test]
2568    fn test_process_mapping_command_unencrypted_no_metadata() {
2569        let block_count = 2;
2570        let mut expected_data = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
2571        for (i, byte) in expected_data.iter_mut().enumerate() {
2572            *byte = (i % 251) as u8;
2573        }
2574        let service = Arc::new(FakeBlockService::new(expected_data.clone()));
2575        let files = Arc::new(Files::new(
2576            service.clone(),
2577            TestDeliveryHandler(|_k, r| TestVecBuffer::new_with_range(r).0),
2578            zx::Port::create(),
2579        ));
2580
2581        let data_extents =
2582            Extents::try_new([Extent::new(0..(block_count as u64 * BLOCK_SIZE), Some(0))], 0)
2583                .unwrap();
2584        let mut payload_bytes = Vec::new();
2585        for w in Extents::encode_extents(&data_extents) {
2586            payload_bytes.extend_from_slice(&w.to_le_bytes());
2587        }
2588
2589        let vmo = zx::Vmo::create(65536).unwrap();
2590        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
2591            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2592            1024,
2593            16,
2594        )
2595        .unwrap();
2596        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2597        payload_buf.data().copy_from_slice(&payload_bytes);
2598        let cmd = crate::RawMappingCommand {
2599            opcode: crate::MAPPINGS_COMMAND,
2600            offset: payload_buf.offset(),
2601            key: 100,
2602            stored_size: block_count as u64 * BLOCK_SIZE,
2603            device_offset: 0,
2604            metadata_count: 0,
2605            extent_count: 1,
2606        };
2607        payload_buf.commit(cmd).unwrap();
2608        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2609
2610        let msg = receiver.peek().unwrap();
2611        process_mapping_command(&msg, &files).unwrap();
2612
2613        let file = files.get_file(100).expect("File should be loaded immediately");
2614        let (page_request, rx) =
2615            TestVecBuffer::new_with_range(0..(block_count as u64 * BLOCK_SIZE));
2616        file.read_range(service.as_ref(), page_request);
2617        assert_eq!(rx.output(), expected_data);
2618    }
2619
2620    #[fuchsia::test]
2621    fn test_process_mapping_command_encrypted() {
2622        struct NoRegisterBlobHandler;
2623        impl DeliveryHandler for NoRegisterBlobHandler {
2624            type Request = TestVecBuffer;
2625            fn get_page_request(self: &Arc<Self>, _key: u64, range: Range<u64>) -> Self::Request {
2626                TestVecBuffer::new_with_range(range).0
2627            }
2628            fn register_blob(
2629                &self,
2630                _key: u64,
2631                _merkle_leaves: &[[u8; 32]],
2632                _read_alignment: usize,
2633            ) -> Result<(), Error> {
2634                panic!("register_blob should not be called for encrypted files");
2635            }
2636        }
2637
2638        let block_count = 2;
2639        let mut plaintext = vec![0u8; (block_count as u64 * BLOCK_SIZE) as usize];
2640        for (i, byte) in plaintext.iter_mut().enumerate() {
2641            *byte = (i % 251) as u8;
2642        }
2643
2644        let raw_key = [0x5au8; 32];
2645        let key = UnwrappedKey::new(raw_key.to_vec());
2646        let cipher = FxfsCipher::new(&key);
2647
2648        let mut ciphertext = plaintext.clone();
2649        cipher.encrypt(0, 0, 0, 0, MutPtrByteSlice::from(&mut ciphertext[..])).unwrap();
2650
2651        let service = Arc::new(FakeBlockService::new(ciphertext));
2652        let files =
2653            Arc::new(Files::new(service.clone(), NoRegisterBlobHandler, zx::Port::create()));
2654
2655        let data_extents =
2656            Extents::try_new([Extent::new(0..(block_count as u64 * BLOCK_SIZE), Some(0))], 0)
2657                .unwrap();
2658        let mut payload_bytes = Vec::new();
2659        for w in Extents::encode_extents(&data_extents) {
2660            payload_bytes.extend_from_slice(&w.to_le_bytes());
2661        }
2662        payload_bytes.extend_from_slice(&raw_key);
2663
2664        let vmo = zx::Vmo::create(65536).unwrap();
2665        let mut sender = vmo_fifo::SyncSender::<crate::RawMappingCommand>::new(
2666            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2667            1024,
2668            16,
2669        )
2670        .unwrap();
2671        let mut payload_buf = sender.reserve_payload(payload_bytes.len()).unwrap();
2672        payload_buf.data().copy_from_slice(&payload_bytes);
2673        let cmd = crate::RawMappingCommand {
2674            opcode: crate::MAPPINGS_COMMAND | crate::MAPPINGS_FLAG_ENCRYPTED,
2675            offset: payload_buf.offset(),
2676            key: 101,
2677            stored_size: block_count as u64 * BLOCK_SIZE,
2678            device_offset: 0,
2679            metadata_count: 0,
2680            extent_count: 1,
2681        };
2682        payload_buf.commit(cmd).unwrap();
2683
2684        let invalid_cmd = crate::RawMappingCommand {
2685            opcode: crate::MAPPINGS_COMMAND | crate::MAPPINGS_FLAG_ENCRYPTED,
2686            offset: 0,
2687            key: 102,
2688            stored_size: block_count as u64 * BLOCK_SIZE,
2689            device_offset: 0,
2690            metadata_count: 1,
2691            extent_count: 1,
2692        };
2693        sender.reserve_payload(0).unwrap().commit(invalid_cmd).unwrap();
2694
2695        let mut receiver = vmo_fifo::Receiver::<crate::RawMappingCommand>::new(vmo, 16).unwrap();
2696
2697        let msg = receiver.peek().unwrap();
2698        process_mapping_command(&msg, &files).unwrap();
2699        msg.pop().unwrap();
2700
2701        let invalid_msg = receiver.peek().unwrap();
2702        assert!(process_mapping_command(&invalid_msg, &files).is_err());
2703
2704        let file = files.get_file(101).expect("File should be loaded immediately");
2705        assert!(matches!(file.transform(), Transform::Encrypted(_)));
2706
2707        let (page_request, rx) =
2708            TestVecBuffer::new_with_range(0..(block_count as u64 * BLOCK_SIZE));
2709        file.read_range(service.as_ref(), page_request);
2710        assert_eq!(rx.output(), plaintext);
2711    }
2712
2713    #[fuchsia::test]
2714    fn test_queued_page_request_does_not_deadlock_single_completion_thread() {
2715        // 128 KiB of blob data (> 64 KiB buffer pool) + 1 block (4 KiB) of metadata.
2716        let data_size = TARGET_READ_AHEAD_SIZE as usize;
2717        let encoded_metadata = serialize_metadata(&BlobMetadata {
2718            merkle_leaves: MerkleLeaves::new(),
2719            format: BlobFormat::Uncompressed,
2720        });
2721        let mut device_data = vec![0xabu8; data_size + BLOCK_SIZE as usize];
2722        device_data[data_size..data_size + encoded_metadata.len()]
2723            .copy_from_slice(&encoded_metadata);
2724
2725        // 64 KiB buffer pool (< 128 KiB TARGET_READ_AHEAD_SIZE).
2726        let block_service = DelayedBlockService::new_with_pool_capacity(device_data, 64 * 1024);
2727
2728        let (page_request, rx) = TestVecBuffer::new_with_range(0..4096);
2729        let page_request = Mutex::new(Some(page_request));
2730        let files = Arc::new(Files::new(
2731            block_service.clone(),
2732            TestDeliveryHandler(move |_key, _range| page_request.lock().take().unwrap()),
2733            zx::Port::create(),
2734        ));
2735        let _pager_thread = files.spawn_pager_thread();
2736
2737        let data_extents =
2738            Extents::try_new([Extent::new(0..TARGET_READ_AHEAD_SIZE, Some(0))], 0).unwrap();
2739        let meta_extents =
2740            Extents::try_new([Extent::new(0..BLOCK_SIZE, Some(TARGET_READ_AHEAD_SIZE))], 0)
2741                .unwrap();
2742
2743        let blob_key = 200;
2744
2745        // Start metadata read (txn 0: 4 KiB). Allocates 4 KiB from the 64 KiB pool and queues txn 0
2746        // in `block_service.pending`.
2747        files.begin_loading(blob_key);
2748        let files_clone = files.clone();
2749        read_blob_metadata(
2750            block_service.as_ref(),
2751            &meta_extents,
2752            TARGET_READ_AHEAD_SIZE,
2753            move |metadata| {
2754                let file = Arc::new(File::new(
2755                    data_extents,
2756                    metadata.uncompressed_size,
2757                    metadata.compression_info.into(),
2758                ));
2759                files_clone.insert(blob_key, file);
2760            },
2761        );
2762
2763        // Queue a page request while txn 0 is still pending. Because `File::read_range` expands
2764        // page requests to `TARGET_READ_AHEAD_SIZE` (128 KiB) and the buffer pool is 64 KiB,
2765        // servicing this request requires two sequential 64 KiB buffer allocations (txn 1 and
2766        // txn 2).
2767        files.handle_page_request(blob_key, 0..4096);
2768
2769        // Complete txn 0 (metadata) followed by txn 1 and txn 2 (the two 64 KiB data chunks) on a
2770        // single completion thread. Completing txn 0 inserts the file, which must not block the
2771        // completion thread while servicing the queued page request.
2772        let block_completion_thread = std::thread::spawn({
2773            let block_service = block_service.clone();
2774            move || {
2775                for _ in 0..3 {
2776                    block_service.wait_and_trigger_sync();
2777                }
2778            }
2779        });
2780
2781        block_completion_thread.join().unwrap();
2782
2783        // Verify that the 128 KiB readahead was split into two 64 KiB block reads (matching the
2784        // 64 KiB pool capacity) and both chunks were committed to the pager.
2785        assert_eq!(rx.wait_for_commits(2), vec![(0, 65536), (65536, 65536)]);
2786        // Verify that the full 128 KiB of blob data was copied into the page request's buffer.
2787        assert_eq!(rx.output(), vec![0xabu8; data_size]);
2788    }
2789
2790    #[fuchsia::test]
2791    fn test_pager_thread_lifecycle() {
2792        let port = zx::Port::create();
2793        let service = Arc::new(FakeBlockService::new(vec![0u8; 4096]));
2794        let files = Arc::new(Files::new(
2795            service,
2796            TestDeliveryHandler(|_key, _range| TestVecBuffer::new(4096).0),
2797            port,
2798        ));
2799        let thread = files.spawn_pager_thread();
2800        drop(thread);
2801    }
2802
2803    #[fuchsia::test]
2804    fn test_pager_packet() {
2805        let port = zx::Port::create();
2806        let pager = zx::Pager::create(zx::PagerOptions::empty()).expect("create pager");
2807        let vmo = pager.create_vmo(zx::VmoOptions::empty(), &port, 1234, 4096).expect("create vmo");
2808
2809        let vmo_clone = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate vmo");
2810        let _reader_thread = std::thread::spawn(move || {
2811            let mut b = [0u8; 1];
2812            let _ = vmo_clone.read(&mut b, 0);
2813        });
2814
2815        let packet = port.wait(zx::MonotonicInstant::INFINITE).expect("wait packet");
2816        assert_eq!(packet.key(), 1234);
2817        if let zx::PacketContents::Pager(pager_packet) = packet.contents() {
2818            assert_eq!(pager_packet.command(), ZX_PAGER_VMO_READ);
2819            assert_eq!(pager_packet.range(), 0..4096);
2820        } else {
2821            panic!("Expected pager packet");
2822        }
2823    }
2824
2825    #[fuchsia::test]
2826    fn test_files_record_profile() {
2827        let total_size = TARGET_READ_AHEAD_SIZE * 2;
2828        let uncompressed_data = vec![0x55u8; total_size as usize];
2829
2830        // Compress a 192 KiB file with 48 KiB chunks so its `read_ahead_size` is 96 KiB
2831        // (`128 KiB / 48 KiB * 48 KiB = 96 KiB`), distinct from `TARGET_READ_AHEAD_SIZE` (128 KiB).
2832        let compressed_uncompressed_size = 192 * 1024;
2833        let chunk_size = 48 * 1024;
2834        let options = ChunkedArchiveOptions::V2 {
2835            minimum_chunk_size: chunk_size,
2836            chunk_alignment: chunk_size,
2837            compression_level: 1,
2838        };
2839        let archive = delivery_blob::compression::ChunkedArchive::new(
2840            &uncompressed_data[..compressed_uncompressed_size],
2841            options,
2842        )
2843        .expect("ChunkedArchive::new failed");
2844        let mut compressed_offsets = vec![0];
2845        let mut compressed_bytes = Vec::new();
2846        for chunk in archive.chunks() {
2847            compressed_bytes.extend_from_slice(&chunk.compressed_data);
2848            compressed_offsets.push(compressed_bytes.len() as u64);
2849        }
2850        compressed_offsets.pop();
2851        let compressed_stored_size = compressed_bytes.len() as u64;
2852        let compressed_stored_aligned = compressed_stored_size.next_multiple_of(BLOCK_SIZE);
2853
2854        let mut device_data = vec![0x55u8; (total_size + compressed_stored_aligned) as usize];
2855        device_data[total_size as usize..total_size as usize + compressed_bytes.len()]
2856            .copy_from_slice(&compressed_bytes);
2857        let service = Arc::new(FakeBlockService::new(device_data));
2858
2859        let files = Arc::new(Files::new(
2860            service,
2861            TestDeliveryHandler(|_key, range| TestVecBuffer::new_with_range(range).0),
2862            zx::Port::create(),
2863        ));
2864
2865        let extents1 = Extents::try_new([Extent::new(0..total_size, Some(0))], 0)
2866            .expect("Extents::try_new failed");
2867        let file1 = Arc::new(File::new(extents1, total_size, Transform::None));
2868        files.insert(10, file1);
2869
2870        let extents2 =
2871            Extents::try_new([Extent::new(0..compressed_stored_aligned, Some(total_size))], 0)
2872                .expect("Extents::try_new failed");
2873        let compression_info = CompressionInfo::new(
2874            chunk_size as u64,
2875            compressed_stored_size,
2876            &compressed_offsets,
2877            CompressionAlgorithm::Zstd,
2878        )
2879        .expect("CompressionInfo::new failed");
2880        let file2 = Arc::new(File::new(
2881            extents2,
2882            compressed_uncompressed_size as u64,
2883            compression_info.into(),
2884        ));
2885        let compressed_read_ahead = file2.read_ahead_size();
2886        assert_eq!(compressed_read_ahead, 96 * 1024);
2887        files.insert(20, file2);
2888
2889        // Stopping when no recording is active should return BAD_STATE.
2890        assert_eq!(
2891            files.stop_recording().expect_err("stop_recording succeeded without active recording"),
2892            zx::Status::BAD_STATE
2893        );
2894
2895        // Page requests before recording starts should not be recorded.
2896        files.handle_page_request(10, 0..4096);
2897
2898        files.start_recording().expect("start_recording failed");
2899        assert_eq!(
2900            files.start_recording().expect_err("duplicate start_recording succeeded"),
2901            zx::Status::BAD_STATE
2902        );
2903
2904        // Fault in first readahead window of key 10 (offset 4096 -> window 0).
2905        files.handle_page_request(10, 4096..8192);
2906        // Fault in second 96 KiB readahead window of compressed key 20 (offset 100 KiB -> 96 KiB).
2907        files.handle_page_request(20, 100 * 1024..104 * 1024);
2908        // Fault spanning both 128 KiB readahead windows of key 10 (0..256 KiB -> 0 and 128 KiB).
2909        files.handle_page_request(10, 0..total_size);
2910
2911        let vmo = files.stop_recording().expect("stop_recording failed");
2912        let entries: Vec<_> = FileMessage::decode_from_vmo(vmo, BLOCK_SIZE as usize)
2913            .expect("decode_from_vmo failed")
2914            .collect();
2915        assert_eq!(
2916            entries,
2917            vec![
2918                FileMessage { id: 10, offset: 0 },
2919                FileMessage { id: 20, offset: compressed_read_ahead },
2920                FileMessage { id: 10, offset: 0 },
2921                FileMessage { id: 10, offset: TARGET_READ_AHEAD_SIZE },
2922            ]
2923        );
2924
2925        // Faults after stop_recording should not be recorded, and a subsequent recording starts
2926        // clean.
2927        files.handle_page_request(20, 0..4096);
2928        files.start_recording().expect("second start_recording failed");
2929        let empty_vmo = files.stop_recording().expect("second stop_recording failed");
2930        assert_eq!(
2931            FileMessage::decode_from_vmo(empty_vmo, BLOCK_SIZE as usize)
2932                .expect("decode_from_vmo failed")
2933                .collect::<Vec<_>>(),
2934            Vec::<FileMessage>::new()
2935        );
2936    }
2937
2938    #[fuchsia::test]
2939    fn test_files_record_profile_deferred_loading() {
2940        let total_size = TARGET_READ_AHEAD_SIZE;
2941        let device_data = vec![0x77u8; total_size as usize];
2942        let service = Arc::new(FakeBlockService::new(device_data));
2943
2944        let (page_request, rx) = TestVecBuffer::new_with_range(4096..8192);
2945        let page_request = Mutex::new(Some(page_request));
2946        let files = Arc::new(Files::new(
2947            service,
2948            TestDeliveryHandler(move |_key, _range| {
2949                page_request.lock().take().expect("page_request already taken")
2950            }),
2951            zx::Port::create(),
2952        ));
2953        let _pager_thread = files.spawn_pager_thread();
2954
2955        files.start_recording().expect("start_recording failed");
2956
2957        // Queue a page request for key 55 before its mapping is inserted.
2958        files.handle_page_request(55, 4096..8192);
2959        assert!(files.has_queued_page_requests(55));
2960
2961        let extents = Extents::try_new([Extent::new(0..total_size, Some(0))], 0)
2962            .expect("Extents::try_new failed");
2963        let file = Arc::new(File::new(extents, total_size, Transform::None));
2964        files.insert(55, file);
2965
2966        assert_eq!(rx.wait_for_commits(1), vec![(0, total_size as usize)]);
2967
2968        let vmo = files.stop_recording().expect("stop_recording failed");
2969        let entries: Vec<_> = FileMessage::decode_from_vmo(vmo, BLOCK_SIZE as usize)
2970            .expect("decode_from_vmo failed")
2971            .collect();
2972        assert_eq!(entries, vec![FileMessage { id: 55, offset: 0 }]);
2973    }
2974
2975    #[test]
2976    fn test_child_range_and_inline_tail() {
2977        #[derive(Debug, PartialEq)]
2978        enum RecordedEvent {
2979            InlineTail {
2980                parent_byte_offset: u64,
2981                child_key: u64,
2982                child_target_offset: u64,
2983                tail_length: u32,
2984            },
2985            ChildPageRequest {
2986                child_key: u64,
2987                child_target_offset: u64,
2988                parent_byte_range: Range<u64>,
2989            },
2990        }
2991
2992        struct RecordingChildHandler {
2993            events: Mutex<Vec<RecordedEvent>>,
2994        }
2995        impl DeliveryHandler for RecordingChildHandler {
2996            type Request = TestVecBuffer;
2997            fn get_page_request(self: &Arc<Self>, _key: u64, range: Range<u64>) -> Self::Request {
2998                TestVecBuffer::new_with_range(range).0
2999            }
3000            fn get_child_page_request(
3001                self: &Arc<Self>,
3002                child_key: u64,
3003                child_target_offset: u64,
3004                parent_byte_range: Range<u64>,
3005            ) -> Self::Request {
3006                self.events.lock().push(RecordedEvent::ChildPageRequest {
3007                    child_key,
3008                    child_target_offset,
3009                    parent_byte_range: parent_byte_range.clone(),
3010                });
3011                TestVecBuffer::new_with_range(parent_byte_range).0
3012            }
3013            fn deliver_child_inline_tail(
3014                self: &Arc<Self>,
3015                parent_byte_offset: u64,
3016                child_key: u64,
3017                child_target_offset: u64,
3018                tail_length: u32,
3019            ) {
3020                self.events.lock().push(RecordedEvent::InlineTail {
3021                    parent_byte_offset,
3022                    child_key,
3023                    child_target_offset,
3024                    tail_length,
3025                });
3026            }
3027        }
3028
3029        let data_size = (TARGET_READ_AHEAD_SIZE * 2) as usize;
3030        let mut expected_data = vec![0u8; data_size];
3031        for (i, byte) in expected_data.iter_mut().enumerate() {
3032            *byte = ((i * 31 + 7) % 251) as u8;
3033        }
3034        let service = FakeBlockService::new(expected_data);
3035        let parent_extents =
3036            Extents::try_new([Extent::new(0..(data_size as u64), Some(0))], 0).unwrap();
3037        let parent_file = Arc::new(File::new(parent_extents, data_size as u64, Transform::None));
3038
3039        // Create a 2-page FlatInline child file whose first page (0..4096) is at parent block
3040        // 16384..20480 and whose inline tail (4096..8192) is at unaligned parent offset 4128.
3041        let child_extents = Extents::try_new(
3042            [
3043                Extent::new(0..4096, Some(16384)),
3044                Extent::new(4096..8192, crate::DeviceExtent::InlineTail(4128)),
3045            ],
3046            0,
3047        )
3048        .unwrap();
3049        let child_file = Arc::new(File::new(child_extents, 4608, Transform::None));
3050
3051        let handler = Arc::new(RecordingChildHandler { events: Mutex::new(Vec::new()) });
3052        let started_async =
3053            child_file.read_child_range(10, 0..4096, &parent_file, &service, &handler);
3054        assert!(started_async, "Expected Block extent to perform one aligned parent read");
3055        assert_eq!(service.read_count(), 1, "InlineTail must not perform any block I/O");
3056
3057        let events = handler.events.lock();
3058        assert_eq!(
3059            &*events,
3060            &[
3061                RecordedEvent::ChildPageRequest {
3062                    child_key: 10,
3063                    child_target_offset: 0,
3064                    parent_byte_range: 16384..20480,
3065                },
3066                RecordedEvent::InlineTail {
3067                    parent_byte_offset: 4128,
3068                    child_key: 10,
3069                    child_target_offset: 4096,
3070                    tail_length: 512,
3071                },
3072            ]
3073        );
3074    }
3075}