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