Skip to main content

blob_pager_and_verifier/
lib.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
5//! Coordinates pager-backed blob VMOs, driver-level page fault handling, and cryptographic
6//! verification.
7//!
8//! # Overview
9//! `BlobPagerAndVerifier` manages pager-backed VMOs for blobs. It decouples page fault servicing
10//! from the filesystem by having the block driver read and decompress raw disk blocks directly,
11//! while `BlobPagerAndVerifier` cryptographically verifies the pages before supplying them to
12//! the pager VMO to unblock client reads.
13//!
14//! # Blob Opening and Caching
15//! When a client requests a blob via `create_vmo`:
16//! - **If already cached**: Clones and returns a new read-only child VMO immediately.
17//! - **If currently opening**: Suspends until the in-flight request finishes, then clones and
18//!   returns a child VMO once ready.
19//! - **If not in cache**: Registers the blob's extent mappings with Fxfs, creates the pager-backed
20//!   VMO, caches it, and returns a child VMO to the caller.
21//!
22//! # Page Fault Handling and Verification
23//! 1. When a client reads an unpopulated page, the kernel generates a page request on the pager
24//!    port, which the block driver monitors directly.
25//! 2. The driver reads and decompresses the disk blocks according to the blob's extent mappings,
26//!    then writes unverified pages and Merkle metadata into a shared delivery queue VMO.
27//! 3. `BlobPagerAndVerifier` verifies the incoming pages against the blob's Merkle tree:
28//!    - On success, it supplies the verified pages to the pager VMO (`zx_pager_supply_pages`),
29//!      unblocking the client read.
30//!    - On verification failure, it fails the request with `ZX_ERR_IO_DATA_INTEGRITY`.
31//!
32//! # Eviction and Teardown
33//! 1. Clients receive read-only child VMOs cloned from the parent pager VMO.
34//! 2. When all client handles to a blob are dropped, the kernel notifies the cache via
35//!    `ZX_VMO_ZERO_CHILDREN`.
36//! 3. The cache evicts the inactive blob and closes its mapping session with Fxfs, reclaiming
37//!    driver extent tracking and kernel pager resources.
38
39mod delivery;
40mod profile;
41
42pub use delivery::{
43    DeliveryQueueProcessor, DeliveryQueueProvider, TestVmoProvider, UnverifiedPages,
44};
45pub use profile::ProfileRecording;
46pub use storage_profile::BOOT_PROFILE_NAME;
47
48use anyhow::{Context, Error, anyhow, bail};
49use event_listener as _;
50use fidl_fuchsia_storage_block as fblock;
51use fidl_fuchsia_storage_mapping as fmapping;
52use fuchsia_async as fasync;
53use fuchsia_hash::{HASH_SIZE, Hash};
54use fuchsia_merkle::{MerkleVerifier, ReadSizedMerkleVerifier};
55use fuchsia_sync::Mutex;
56use futures::TryStreamExt as _;
57use linked_hash_map::LinkedHashMap;
58use std::collections::{HashMap, HashSet, VecDeque, hash_map};
59use std::ops::Range;
60use std::sync::{Arc, OnceLock, Weak};
61use storage_ptr_slice::PtrByteSlice;
62use zx;
63
64/// Default maximum number of unreferenced blobs retained in the LRU cache, matching Fxfs's
65/// `DIRENT_CACHE_LIMIT`.
66const DEFAULT_CACHE_CAPACITY: usize = 2000;
67
68// A `fuchsia_async::PacketReceiver` that watches for a VMO to reach zero children and triggers
69// cache eviction.
70struct ZeroChildrenReceiver {
71    blob: OnceLock<Weak<CachedBlob>>,
72}
73
74impl fasync::PacketReceiver for ZeroChildrenReceiver {
75    fn receive_packet(&self, packet: zx::Packet) {
76        if let zx::PacketContents::SignalOne(signals) = packet.contents() {
77            if signals.observed().contains(zx::Signals::VMO_ZERO_CHILDREN) {
78                if let Some(cached_blob) = self.blob.get().and_then(|w| w.upgrade()) {
79                    if let Some(cache) = cached_blob.cache.upgrade() {
80                        cache.on_zero_children(&cached_blob);
81                    }
82                }
83            }
84        }
85    }
86}
87
88#[derive(Default)]
89struct InlineTailQueue {
90    busy: bool,
91    pending: VecDeque<delivery::ChildInlineTail>,
92}
93
94// Holds the state and resources for an open blob residing in the cache.
95//
96// When a blob is opened, this bundles:
97// - The root pager VMO that clients clone from (so we can detect when all handles close).
98// - The Fxfs session key used by the block driver to deliver data and to close the mapping.
99// - The Merkle tree verifier used to check incoming pages from the driver.
100// - The ZeroChildrenReceiver registration that watches for zero children to trigger eviction.
101struct CachedBlob {
102    // The parent pager-backed VMO. Clients only receive child clones so the kernel can notify us
103    // via `ZX_VMO_ZERO_CHILDREN` when all clients have dropped their handles.
104    vmo: zx::Vmo,
105    // The Merkle root hash of the blob (its unique identifier).
106    root_hash: [u8; 32],
107    // Weak back-reference to Cache to avoid an Arc reference cycle, since the cache already
108    // holds an Arc to this blob.
109    cache: Weak<Cache>,
110    // Session key assigned by Fxfs for this blob mapping, used in driver delivery commands and when
111    // closing the session.
112    vmo_key: u64,
113    // Blob size in bytes.
114    len: u64,
115    // Keeps ZeroChildrenReceiver registered on the async loop while this blob is alive.
116    registration: fasync::ReceiverRegistration<ZeroChildrenReceiver>,
117    // Merkle tree verifier, initialised once the driver delivers the blob's leaf hashes.
118    merkle_verifier: OnceLock<ReadSizedMerkleVerifier>,
119}
120
121impl CachedBlob {
122    fn create_child(&self) -> Result<zx::Vmo, zx::Status> {
123        self.vmo.create_child(zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE, 0, 0)
124    }
125
126    fn wait_for_zero_children(&self) {
127        let _ = self.vmo.wait_async(
128            fasync::EHandle::local().port(),
129            self.registration.key(),
130            zx::Signals::VMO_ZERO_CHILDREN,
131            zx::WaitAsyncOpts::empty(),
132        );
133    }
134}
135
136impl Drop for CachedBlob {
137    fn drop(&mut self) {
138        if let Some(cache) = self.cache.upgrade() {
139            let session = cache.mapping_session.clone();
140            let vmo_key = self.vmo_key;
141            cache.scope.spawn(async move {
142                let _ = session.close(vmo_key).await;
143            });
144        }
145    }
146}
147
148enum BlobState {
149    // The blob is actively opening: extents are being registered with Fxfs and the pager VMO is
150    // being created. Concurrent requests wait on `event`. Early Merkle leaves delivered by the
151    // driver are placed directly into `verifier`.
152    Opening {
153        root_hash: [u8; 32],
154        event: Arc<event_listener::Event>,
155        verifier: Option<ReadSizedMerkleVerifier>,
156    },
157    // The blob's pager-backed VMO is created and cached. Eviction is triggered when all client
158    // child VMOs are closed (`ZX_VMO_ZERO_CHILDREN`).
159    Ready(Arc<CachedBlob>),
160}
161
162pub(crate) struct ActiveRecording {
163    // Blobs opened by external clients during the active recording window.
164    pub(crate) opened_hashes: HashSet<Hash>,
165    // Maps session key to Merkle root hash for all blobs opened or accessed during the recording
166    // window, retained until recording stops even if a blob is evicted before then. When all child
167    // VMO handles are dropped (`ZX_VMO_ZERO_CHILDREN`), `Cache` evicts the blob and closes its
168    // session key; if the same blob is later opened again during the same recording window, `Cache`
169    // allocates a new monotonically increasing session key for it. Retaining every key to `Hash`
170    // mapping ensures page requests recorded by the driver under any of those keys resolve back to
171    // the blob's `Hash` when recording stops.
172    pub(crate) hash_by_key: HashMap<u64, Hash>,
173}
174
175// Holds a pager-backed VMO created for a file in a child `MapperSession` (e.g. an EROFS file
176// mapped inside a parent blob) along with its parent blob key and unaligned logical byte size.
177struct ChildVmo {
178    parent_key: u64,
179    vmo: zx::Vmo,
180    len: u64,
181}
182
183struct CacheState {
184    next_key: u64,
185    // Maps blob Merkle root hash to the client-allocated mapping session key.
186    keys_by_hash: HashMap<[u8; 32], u64>,
187    blob_states_by_key: HashMap<u64, BlobState>,
188    recording: Option<ActiveRecording>,
189    // Maps `child_vmo_key` to the child VMO registered via
190    // `BlobPagerAndVerifier::open_child_session`.
191    child_vmos: HashMap<u64, Arc<ChildVmo>>,
192    // LRU map of `vmo_key`s for `Ready` blobs that currently have zero open child VMOs.
193    unreferenced_lru: LinkedHashMap<u64, ()>,
194}
195
196impl Default for CacheState {
197    fn default() -> Self {
198        Self {
199            next_key: 1,
200            keys_by_hash: HashMap::default(),
201            blob_states_by_key: HashMap::default(),
202            recording: None,
203            child_vmos: HashMap::default(),
204            unreferenced_lru: LinkedHashMap::default(),
205        }
206    }
207}
208
209impl CacheState {
210    fn generate_key(&mut self) -> u64 {
211        let key = self.next_key;
212        self.next_key += 1;
213        key
214    }
215
216    fn evict_excess_unreferenced(&mut self, capacity: usize) -> Vec<Arc<CachedBlob>> {
217        let mut evicted = Vec::new();
218        while self.unreferenced_lru.len() > capacity {
219            let Some((evict_key, ())) = self.unreferenced_lru.pop_front() else {
220                break;
221            };
222            if let Some(BlobState::Ready(cached)) = self.blob_states_by_key.remove(&evict_key) {
223                if let hash_map::Entry::Occupied(entry) = self.keys_by_hash.entry(cached.root_hash)
224                {
225                    if *entry.get() == evict_key {
226                        entry.remove();
227                    }
228                }
229                evicted.push(cached);
230            }
231        }
232        evicted
233    }
234}
235
236// Holds a pending reservation for a blob actively being opened.
237//
238// If dropped before `commit` is called (e.g. due to an early FIDL failure, pager VMO creation
239// failure, or cancellation), the `Drop` implementation rolls back the reservation: it evicts the
240// key from the cache, queues a close request with Fxfs, and unblocks waiting tasks.
241struct PendingEntry {
242    cache: Arc<Cache>,
243    key: u64,
244}
245
246impl PendingEntry {
247    fn key(&self) -> u64 {
248        self.key
249    }
250
251    // Fulfills the pending entry by constructing the `CachedBlob`, atomically transitioning the
252    // cache entry from `Opening` to `Ready`, and notifying waiting tasks.
253    fn commit(self, vmo: zx::Vmo, size: u64) -> Arc<CachedBlob> {
254        let zero_children_receiver = ZeroChildrenReceiver { blob: OnceLock::new() };
255        let zero_children_registration =
256            fasync::EHandle::local().register_receiver(zero_children_receiver);
257
258        let (cached, event) = {
259            let mut state = self.cache.state.lock();
260            let (root_hash, event, verifier) = match state.blob_states_by_key.remove(&self.key) {
261                Some(BlobState::Opening { root_hash, event, verifier }) => {
262                    (root_hash, event, verifier)
263                }
264                _ => {
265                    unreachable!("Blob for key {} was unexpectedly not in Opening state", self.key)
266                }
267            };
268
269            let merkle_verifier = OnceLock::new();
270            if let Some(verifier) = verifier {
271                let _ = merkle_verifier.set(verifier);
272            }
273
274            let cached = Arc::new(CachedBlob {
275                vmo,
276                root_hash,
277                cache: Arc::downgrade(&self.cache),
278                vmo_key: self.key,
279                len: size,
280                registration: zero_children_registration,
281                merkle_verifier,
282            });
283
284            cached
285                .registration
286                .receiver()
287                .blob
288                .set(Arc::downgrade(&cached))
289                .expect("Failed to bind CachedBlob to ZeroChildrenReceiver: already initialized");
290
291            state.blob_states_by_key.insert(self.key, BlobState::Ready(cached.clone()));
292            if let Some(recording) = state.recording.as_mut() {
293                recording.hash_by_key.insert(self.key, Hash::from(root_hash));
294            }
295
296            (cached, event)
297        };
298
299        cached.wait_for_zero_children();
300        event.notify(usize::MAX);
301        cached
302    }
303}
304
305impl Drop for PendingEntry {
306    fn drop(&mut self) {
307        let event = {
308            let mut state = self.cache.state.lock();
309            match state.blob_states_by_key.get(&self.key) {
310                // If the blob committed successfully, there is nothing to roll back.
311                Some(BlobState::Ready(_)) => return,
312                Some(BlobState::Opening { .. }) => {
313                    if let Some(BlobState::Opening { root_hash, event, .. }) =
314                        state.blob_states_by_key.remove(&self.key)
315                    {
316                        state.keys_by_hash.remove(&root_hash);
317                        event
318                    } else {
319                        return;
320                    }
321                }
322                None => {
323                    log::error!(
324                        key = self.key;
325                        "PendingEntry::drop: key unexpectedly absent from cache"
326                    );
327                    return;
328                }
329            }
330        };
331
332        // Queue the close on Fxfs before waking waiters.
333        // Note: If `open()` was never called or returned an error, closing this key is safe
334        // because the block driver ignores `CloseBlob` for unknown keys. However, if `open()` was
335        // in-flight when cancelled, or if `open()` succeeded but subsequent operations failed, this
336        // close prevents leaking extent mappings.
337        let session = self.cache.mapping_session.clone();
338        let key = self.key;
339        self.cache.scope.spawn(async move {
340            let _ = session.close(key).await;
341        });
342
343        // Wake up all suspended tasks concurrently waiting on this blob.
344        event.notify(usize::MAX);
345    }
346}
347
348// The outcome of looking up or reserving an entry in `Cache`.
349enum CacheLookup {
350    // The VMO has been created and cached. Returns a child of it.
351    Ready(zx::Vmo),
352    // The VMO is currently being created by another request. Contains a listener to await
353    // completion.
354    Pending(event_listener::EventListener),
355    // The blob is absent. Provides a drop-safe pending entry to be used with creation. If the task
356    // crashes or returns an early error, the pending state will be cleared from the cache.
357    Missing(PendingEntry),
358}
359
360// Coordinates the concurrent creation, caching, and verification of pager-backed blobs.
361pub(crate) struct Cache {
362    state: Mutex<CacheState>,
363    pub(crate) mapping_session: fmapping::MappingSessionProxy,
364    pager: Arc<zx::Pager>,
365    port: zx::Port,
366    delivery_vmo: zx::Vmo,
367    pub(crate) scope: fasync::ScopeHandle,
368    cache_capacity: usize,
369    // Reusable transfer VMOs for supplying `ChildInlineTail` pages.
370    transfer_vmos: Mutex<Vec<zx::Vmo>>,
371    // Leader-follower queue for servicing `ChildInlineTail` commands so at most one delivery worker
372    // thread ever blocks on an evicted or unsupplied parent VMO page.
373    inline_tail_queue: Mutex<InlineTailQueue>,
374}
375
376const MAX_POOLED_TRANSFER_VMOS: usize = 8;
377
378// Extracts a `zx::Status` from `err`, defaulting to `zx::Status::BAD_STATE` if none is present.
379fn status_from_error(err: &Error) -> zx::Status {
380    err.downcast_ref::<zx::Status>().copied().unwrap_or(zx::Status::BAD_STATE)
381}
382
383impl Cache {
384    fn new(
385        mapping_session: fmapping::MappingSessionProxy,
386        pager: Arc<zx::Pager>,
387        port: zx::Port,
388        delivery_vmo: zx::Vmo,
389        scope: fasync::ScopeHandle,
390        cache_capacity: usize,
391    ) -> Self {
392        Self {
393            state: Mutex::new(CacheState::default()),
394            mapping_session,
395            pager,
396            port,
397            delivery_vmo,
398            scope,
399            cache_capacity,
400            transfer_vmos: Mutex::new(Vec::new()),
401            inline_tail_queue: Mutex::new(InlineTailQueue::default()),
402        }
403    }
404
405    fn delivery_vmo(&self) -> &zx::Vmo {
406        &self.delivery_vmo
407    }
408
409    fn get_by_key(&self, key: u64) -> Option<Arc<CachedBlob>> {
410        let state = self.state.lock();
411        match state.blob_states_by_key.get(&key) {
412            Some(BlobState::Ready(cached)) => Some(cached.clone()),
413            _ => None,
414        }
415    }
416
417    // Registers a pager-backed child VMO (`child_key`) with logical byte size `len` under the
418    // parent blob `parent_key`.
419    fn register_child_vmo(&self, parent_key: u64, child_key: u64, vmo: zx::Vmo, len: u64) {
420        self.state.lock().child_vmos.insert(child_key, Arc::new(ChildVmo { parent_key, vmo, len }));
421    }
422
423    // Removes the child VMO registered under `child_key`.
424    fn unregister_child_vmo(&self, child_key: u64) {
425        self.state.lock().child_vmos.remove(&child_key);
426    }
427
428    // Looks up the child VMO registered under `child_key`.
429    fn get_child_vmo(&self, child_key: u64) -> Option<Arc<ChildVmo>> {
430        self.state.lock().child_vmos.get(&child_key).cloned()
431    }
432
433    // Borrows an unbounded temporary transfer VMO from `self.transfer_vmos`, or creates a new one
434    // if none is available in the pool.
435    fn acquire_transfer_vmo(&self) -> Result<zx::Vmo, zx::Status> {
436        if let Some(vmo) = self.transfer_vmos.lock().pop() {
437            return Ok(vmo);
438        }
439        zx::Vmo::create_with_opts(zx::VmoOptions::UNBOUNDED, 0)
440    }
441
442    // Returns a temporary transfer VMO to `self.transfer_vmos` if the pool is not full.
443    fn release_transfer_vmo(&self, vmo: zx::Vmo) {
444        let mut pool = self.transfer_vmos.lock();
445        if pool.len() < MAX_POOLED_TRANSFER_VMOS {
446            pool.push(vmo);
447        }
448    }
449
450    // Writes `valid_bytes` into a pooled unbounded transfer VMO and supplies `child_range` to
451    // `child_vmo`. `zx_pager_supply_pages` supplies zeroes for any uncommitted trailing bytes and
452    // decommits the supplied range in `transfer_vmo` upon success.
453    fn supply_slice_via_transfer_vmo(
454        &self,
455        child_vmo: &zx::Vmo,
456        child_range: Range<u64>,
457        valid_bytes: &[u8],
458    ) -> Result<(), zx::Status> {
459        let bounded_len = (child_range.end - child_range.start) as usize;
460        let copy_len = std::cmp::min(valid_bytes.len(), bounded_len);
461        let transfer_vmo = self.acquire_transfer_vmo()?;
462        if copy_len > 0 {
463            transfer_vmo.write(&valid_bytes[..copy_len], 0)?;
464        }
465        self.pager.supply_pages(child_vmo, child_range, &transfer_vmo, 0)?;
466        self.release_transfer_vmo(transfer_vmo);
467        Ok(())
468    }
469
470    // Validates alignment/bounds of a parent blob chunk at `[offset .. offset + length)` and
471    // verifies it against `parent_key`'s Merkle tree at `parent_offset`.
472    fn verify_parent_chunk(
473        &self,
474        parent_key: u64,
475        parent_offset: u64,
476        offset: u32,
477        length: u32,
478        payload: Option<PtrByteSlice<'_>>,
479    ) -> Result<(), Error> {
480        let page_size = zx::system_get_page_size() as u64;
481        let page_size_u32 = page_size as u32;
482        let Some(parent_slice) = payload else {
483            return Err(
484                Error::from(zx::Status::INVALID_ARGS).context("ChildData payload out of bounds")
485            );
486        };
487        if offset % page_size_u32 != 0 {
488            return Err(Error::from(zx::Status::INVALID_ARGS)
489                .context(format!("ChildData payload offset must be page aligned: {offset}")));
490        }
491        if length % page_size_u32 != 0 {
492            return Err(Error::from(zx::Status::INVALID_ARGS)
493                .context(format!("ChildData payload length must be page aligned: {length}")));
494        }
495        if parent_offset % page_size != 0 {
496            return Err(Error::from(zx::Status::INVALID_ARGS).context(format!(
497                "ChildData parent_offset must be page aligned: {parent_offset}"
498            )));
499        }
500
501        let cached_blob = self.get_by_key(parent_key).ok_or_else(|| {
502            Error::from(zx::Status::BAD_STATE).context(format!(
503                "Unknown or expired parent key {parent_key} in deliver_child_pages"
504            ))
505        })?;
506        let verifier = cached_blob.merkle_verifier.get().ok_or_else(|| {
507            Error::from(zx::Status::BAD_STATE)
508                .context(format!("ChildData received for uninitialized parent blob {parent_key}"))
509        })?;
510        let remaining_in_parent = cached_blob.len.saturating_sub(parent_offset);
511        let unaligned_parent_len = std::cmp::min(length as u64, remaining_in_parent) as usize;
512        if let Err(e) =
513            verifier.verify_aligned(parent_offset as usize, parent_slice, unaligned_parent_len)
514        {
515            return Err(Error::from(zx::Status::IO_DATA_INTEGRITY)
516                .context(format!("Failed to verify parent payload for blob {parent_key}: {e:?}")));
517        }
518        Ok(())
519    }
520
521    // Reads `data.tail_length` bytes from `data.parent_byte_offset` in the verified parent blob VMO
522    // (`child_vmo.parent_key`) and supplies a zero-padded page to `data.child_key` at
523    // `data.child_target_offset`.
524    //
525    // DANGER: This function is reentrant! Reading from `cached_blob.vmo` can trigger a kernel page
526    // fault on the parent blob if the page has been evicted or not yet supplied. That fault is
527    // routed to the block driver and fulfilled by a `DeliveryCommand::Data` message on the same
528    // delivery queue. Before calling this function, the caller must pop the `ChildInlineTail`
529    // message from the FIFO and ensure another delivery worker thread is available (and woken via
530    // `receiver.will_maybe_reenter()`) to service incoming `DeliveryCommand::Data` messages.
531    fn process_one_inline_tail(&self, data: delivery::ChildInlineTail) -> Result<(), Error> {
532        let Some(child_vmo) = self.get_child_vmo(data.child_key) else {
533            bail!("Unknown child VMO {}", data.child_key);
534        };
535        let parent_key = child_vmo.parent_key;
536        let page_size = zx::system_get_page_size() as u64;
537        if data.child_target_offset % page_size != 0 {
538            bail!(
539                "ChildInlineTail child_target_offset must be page aligned: {}",
540                data.child_target_offset
541            );
542        }
543        if data.tail_length == 0 || (data.tail_length as u64) > page_size {
544            bail!("ChildInlineTail invalid tail_length: {}", data.tail_length);
545        }
546        let child_page_aligned_size = child_vmo.len.next_multiple_of(page_size);
547        if data.child_target_offset >= child_page_aligned_size {
548            return Ok(());
549        }
550        let child_range = data.child_target_offset..data.child_target_offset + page_size;
551
552        let res: Result<(), Error> = (|| {
553            let cached_blob = self.get_by_key(parent_key).ok_or_else(|| {
554                Error::from(zx::Status::BAD_STATE).context(format!(
555                    "Unknown or expired parent key {parent_key} in deliver_child_inline_tail",
556                ))
557            })?;
558            let valid_in_child = child_vmo.len.saturating_sub(data.child_target_offset);
559            let copy_len = std::cmp::min(data.tail_length as u64, valid_in_child) as usize;
560            let mut page = [0u8; 4096];
561            if copy_len > 0 {
562                if data.parent_byte_offset.checked_add(copy_len as u64).unwrap_or(u64::MAX)
563                    > cached_blob.len
564                {
565                    return Err(Error::from(zx::Status::IO_DATA_INTEGRITY)
566                        .context("ChildInlineTail parent_byte_offset out of parent blob bounds"));
567                }
568                // DANGER: Reading from the pager-backed parent VMO is reentrant: if the parent page
569                // is not resident, this blocks the current delivery worker thread on a page fault
570                // that must be resolved by another delivery worker thread processing
571                // `DeliveryCommand::Data` on the same delivery queue. Do not hold any locks needed
572                // by `deliver_pages` across this read.
573                cached_blob
574                    .vmo
575                    .read(&mut page[..copy_len], data.parent_byte_offset)
576                    .context("Failed to read inline tail from parent VMO")?;
577            }
578            self.supply_slice_via_transfer_vmo(
579                &child_vmo.vmo,
580                child_range.clone(),
581                &page[..copy_len],
582            )
583            .context("Failed to supply inline tail page")?;
584            Ok(())
585        })();
586
587        if let Err(err) = res {
588            self.report_pager_failure(&child_vmo.vmo, child_range, status_from_error(&err));
589            return Err(err);
590        }
591        Ok(())
592    }
593
594    #[cfg(test)]
595    fn is_merkle_initialized(&self, key: u64) -> bool {
596        let state = self.state.lock();
597        match state.blob_states_by_key.get(&key) {
598            Some(BlobState::Opening { verifier, .. }) => verifier.is_some(),
599            Some(BlobState::Ready(blob)) => blob.merkle_verifier.get().is_some(),
600            None => false,
601        }
602    }
603
604    pub(crate) fn start_recording(&self) -> Result<(), zx::Status> {
605        let mut state = self.state.lock();
606        if state.recording.is_some() {
607            return Err(zx::Status::BAD_STATE);
608        }
609        let hash_by_key =
610            state.keys_by_hash.iter().map(|(&hash, &key)| (key, Hash::from(hash))).collect();
611        state.recording = Some(ActiveRecording { opened_hashes: HashSet::new(), hash_by_key });
612        Ok(())
613    }
614
615    fn record_open(&self, identifier: &[u8; 32]) {
616        let mut state = self.state.lock();
617        if let Some(recording) = state.recording.as_mut() {
618            recording.opened_hashes.insert(Hash::from(*identifier));
619        }
620    }
621
622    pub(crate) fn take_recording(&self) -> Option<ActiveRecording> {
623        self.state.lock().recording.take()
624    }
625
626    pub(crate) fn abort_recording(&self) {
627        self.state.lock().recording = None;
628    }
629
630    /// Looks up the blob in the cache and returns its current [`CacheLookup`] state:
631    ///
632    /// - [`CacheLookup::Ready`]: The blob is already cached. Returns a new read-only child VMO.
633    /// - [`CacheLookup::Pending`]: Another task is actively opening this blob. Returns an
634    ///   `EventListener` so the caller can await completion without duplicate work.
635    /// - [`CacheLookup::Missing`]: The blob is absent. Atomically reserves the key and slot
636    ///   by inserting `BlobState::Opening`, returning a [`PendingEntry`] for the caller to fulfill.
637    fn lookup(self: &Arc<Self>, identifier: &[u8; 32]) -> Result<CacheLookup, Error> {
638        let mut state = self.state.lock();
639        if let Some(&key) = state.keys_by_hash.get(identifier) {
640            let was_unreferenced = state.unreferenced_lru.remove(&key).is_some();
641            match state.blob_states_by_key.get(&key) {
642                Some(BlobState::Ready(cached)) => {
643                    let child = cached
644                        .create_child()
645                        .map_err(|s| anyhow!("Failed to create child VMO: {s}"))?;
646                    if was_unreferenced {
647                        cached.wait_for_zero_children();
648                    }
649                    return Ok(CacheLookup::Ready(child));
650                }
651                Some(BlobState::Opening { event, .. }) => {
652                    return Ok(CacheLookup::Pending(event.listen()));
653                }
654                None => unreachable!("keys_by_hash pointed to non-existent key {key}"),
655            }
656        }
657
658        let event = Arc::new(event_listener::Event::new());
659        let key = state.generate_key();
660        state.keys_by_hash.insert(*identifier, key);
661        state
662            .blob_states_by_key
663            .insert(key, BlobState::Opening { root_hash: *identifier, event, verifier: None });
664        Ok(CacheLookup::Missing(PendingEntry { cache: self.clone(), key }))
665    }
666
667    pub(crate) async fn open_vmo(
668        self: &Arc<Self>,
669        identifier: &[u8; 32],
670    ) -> Result<zx::Vmo, Error> {
671        loop {
672            let entry = match self.lookup(identifier)? {
673                CacheLookup::Ready(child) => return Ok(child),
674                CacheLookup::Pending(listener) => {
675                    listener.await;
676                    continue; // Re-evaluate cache now that the blocking event triggered
677                }
678                CacheLookup::Missing(entry) => entry,
679            };
680
681            let key = entry.key();
682
683            // Ask Fxfs to register the blob and write its extent mappings into the shared mapping
684            // VMO, using the client-allocated key.
685            let size = self
686                .mapping_session
687                .open(key, identifier)
688                .await
689                .context("FIDL error calling MappingSession.Open")?
690                .map_err(|e| {
691                    Error::from(zx::Status::err_from_raw(e))
692                        .context("MappingSession.Open failed for blob")
693                })?;
694
695            let vmo = self.pager.create_vmo(zx::VmoOptions::empty(), &self.port, key, size)?;
696            let vmo_name = format!("blob-{}", &Hash::from(*identifier).to_string()[..8]);
697            if let Ok(name) = zx::Name::new(&vmo_name) {
698                let _ = vmo.set_name(&name);
699            }
700
701            // Create the initial child. We vend children of this VMO to clients so we can track
702            // when all children are dropped to evict the blob from cache.
703            let first_child = vmo
704                .create_child(zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE, 0, 0)
705                .map_err(|s| anyhow!("Failed to create child VMO: {}", s))?;
706
707            entry.commit(vmo, size);
708
709            return Ok(first_child);
710        }
711    }
712
713    fn on_zero_children(&self, blob: &Arc<CachedBlob>) {
714        // When a blob reaches zero children, move it into the unreferenced LRU queue and evict
715        // the oldest unreferenced blob only if the LRU exceeds `self.cache_capacity`.
716        // We must hold this lock before checking `num_children` so `lookup` cannot clone
717        // a child between this check and the following LRU transition/eviction.
718        let evicted = {
719            let mut state = self.state.lock();
720            if let Ok(info) = blob.vmo.info() {
721                if info.num_children > 0 {
722                    // A concurrent client acquired a child VMO before we processed this packet.
723                    // Resume watching for the next ZERO_CHILDREN signal.
724                    blob.wait_for_zero_children();
725                    return;
726                }
727            }
728            let is_current = matches!(
729                state.blob_states_by_key.get(&blob.vmo_key),
730                Some(BlobState::Ready(cached)) if Arc::ptr_eq(cached, blob)
731            );
732            if !is_current {
733                return;
734            }
735            state.unreferenced_lru.insert(blob.vmo_key, ());
736            state.evict_excess_unreferenced(self.cache_capacity)
737        };
738        drop(evicted);
739    }
740
741    fn create_merkle_verifier(
742        root_hash: &[u8; 32],
743        vmo_key: u64,
744        hashes: Box<[Hash]>,
745        read_alignment: usize,
746    ) -> Result<ReadSizedMerkleVerifier, Error> {
747        // For blobs <= 8192 bytes (a single block), the Merkle tree consists of only a single leaf,
748        // which is identical to the root hash itself. Fxfs omits storing leaf hashes on disk for
749        // single-block blobs to save space, resulting in empty leaf metadata read by the block
750        // server. In that case, synthesise the single leaf from the blob's Merkle root hash.
751        let hashes = if hashes.is_empty() { Box::new([Hash::from(*root_hash)]) } else { hashes };
752
753        match MerkleVerifier::new(Hash::from(*root_hash), hashes) {
754            Ok(verifier) => ReadSizedMerkleVerifier::new(verifier, read_alignment)
755                .map_err(|e| anyhow!("Failed to create ReadSizedMerkleVerifier: {e:?}")),
756            Err(e) => {
757                bail!("Failed to verify merkle leaves for key {vmo_key}: {e:?}");
758            }
759        }
760    }
761
762    fn report_pager_failure(&self, vmo: &zx::Vmo, range: Range<u64>, status: zx::Status) {
763        // Map to the set of statuses accepted by ZX_PAGER_OP_FAIL which are ZX_ERR_IO,
764        // ZX_ERR_IO_DATA_INTEGRITY, ZX_ERR_BAD_STATE, ZX_ERR_NO_SPACE, and ZX_ERR_BUFFER_TOO_SMALL.
765        let pager_status = match status {
766            zx::Status::IO_DATA_INTEGRITY => zx::Status::IO_DATA_INTEGRITY,
767            zx::Status::NO_SPACE => zx::Status::NO_SPACE,
768            zx::Status::BUFFER_TOO_SMALL | zx::Status::FILE_BIG => zx::Status::BUFFER_TOO_SMALL,
769            zx::Status::IO
770            | zx::Status::IO_DATA_LOSS
771            | zx::Status::IO_INVALID
772            | zx::Status::IO_MISSED_DEADLINE
773            | zx::Status::IO_NOT_PRESENT
774            | zx::Status::IO_OVERRUN
775            | zx::Status::IO_REFUSED
776            | zx::Status::PEER_CLOSED => zx::Status::IO,
777            _ => zx::Status::BAD_STATE,
778        };
779
780        // `ZX_PAGER_OP_FAIL` resolves pending page requests overlapping the specified range with
781        // the failure status. Pages that were already supplied remain accessible.
782        if let Err(error) = self.pager.op_range(zx::PagerOp::Fail(pager_status), vmo, range) {
783            log::error!(error:?; "Failed to report pager failure to kernel");
784        }
785    }
786}
787
788/// Bundles the context for a child VMO page delivery and reports a pager failure on `Drop`
789/// unless [`ChildPagesDelivery::deliver`] succeeds in supplying the pages.
790struct ChildPagesDelivery<'a> {
791    cache: &'a Cache,
792    child_vmo: Arc<ChildVmo>,
793    child_range: Range<u64>,
794    data: delivery::ChildData<'a>,
795    status: Option<zx::Status>,
796}
797
798impl<'a> ChildPagesDelivery<'a> {
799    // Validates target alignment and bounds `data` to the child VMO's page-aligned size. Returns
800    // `Ok(None)` if `child_target_offset` is already at or beyond the end of the child VMO.
801    fn new(cache: &'a Cache, data: delivery::ChildData<'a>) -> Result<Option<Self>, Error> {
802        let child_vmo = cache
803            .get_child_vmo(data.child_key)
804            .ok_or_else(|| anyhow!("Unknown child VMO {}", data.child_key))?;
805
806        let page_size = zx::system_get_page_size() as u64;
807        if data.child_target_offset % page_size != 0 {
808            bail!(
809                "ChildData child_target_offset must be page aligned: {}",
810                data.child_target_offset
811            );
812        }
813        if data.child_length == 0 || (data.child_length as u64) % page_size != 0 {
814            bail!(
815                "ChildData child_length must be non-zero and page aligned: {}",
816                data.child_length
817            );
818        }
819
820        let child_page_aligned_size = child_vmo.len.next_multiple_of(page_size);
821        let bounded_child_len = std::cmp::min(
822            data.child_length as u64,
823            child_page_aligned_size.saturating_sub(data.child_target_offset),
824        );
825        if bounded_child_len == 0 {
826            return Ok(None);
827        }
828        let child_range = data.child_target_offset..data.child_target_offset + bounded_child_len;
829        Ok(Some(Self { cache, child_vmo, child_range, data, status: Some(zx::Status::BAD_STATE) }))
830    }
831
832    // Supplies `self.child_range` in `self.child_vmo` from `source_vmo` at `source_offset` and
833    // clears `self.status` so `Drop` does not report a pager failure.
834    fn supply_pages(&mut self, source_vmo: &zx::Vmo, source_offset: u64) -> Result<(), Error> {
835        match self.cache.pager.supply_pages(
836            &self.child_vmo.vmo,
837            self.child_range.clone(),
838            source_vmo,
839            source_offset,
840        ) {
841            Ok(()) => {
842                self.status = None;
843                Ok(())
844            }
845            Err(status) => {
846                self.status = Some(status);
847                bail!("Failed to supply child pages: {status:?}");
848            }
849        }
850    }
851
852    // Verifies the parent chunk, zeroes any trailing bytes beyond `self.child_vmo.len`, and
853    // supplies the pages to `self.child_vmo`.
854    fn deliver(mut self) -> Result<(), Error> {
855        let page_size = zx::system_get_page_size() as u64;
856        let page_size_u32 = page_size as u32;
857        let bounded_child_len = self.child_range.end - self.child_range.start;
858
859        if self.data.length == 0 {
860            // A zero-length parent payload indicates a sparse extent in the child file.
861            let transfer_vmo = self.cache.acquire_transfer_vmo()?;
862            self.supply_pages(&transfer_vmo, 0)?;
863            self.cache.release_transfer_vmo(transfer_vmo);
864            return Ok(());
865        }
866
867        if let Err(err) = self.cache.verify_parent_chunk(
868            self.child_vmo.parent_key,
869            self.data.parent_offset,
870            self.data.offset,
871            self.data.length,
872            self.data.payload.as_ref().map(|p| p.as_ptr_slice()),
873        ) {
874            self.status = Some(status_from_error(&err));
875            return Err(err);
876        }
877
878        if self.data.in_chunk_offset % page_size_u32 != 0 {
879            bail!("ChildData in_chunk_offset must be page aligned: {}", self.data.in_chunk_offset);
880        }
881        if self.data.in_chunk_offset.checked_add(bounded_child_len as u32).unwrap_or(u32::MAX)
882            > self.data.length
883        {
884            bail!("Child slice out of parent chunk bounds");
885        }
886        let source_offset = self.data.offset + self.data.in_chunk_offset;
887
888        // Zero any bytes beyond the child file's logical size (`child_vmo.len`).
889        if self.child_range.end > self.child_vmo.len {
890            let valid_in_slice =
891                self.child_vmo.len.saturating_sub(self.data.child_target_offset) as usize;
892            let bounded_usize = bounded_child_len as usize;
893            if valid_in_slice < bounded_usize {
894                let in_chunk = self.data.in_chunk_offset as usize;
895                self.data
896                    .payload
897                    .as_mut()
898                    .unwrap()
899                    .subslice_mut((in_chunk + valid_in_slice)..(in_chunk + bounded_usize))
900                    .fill(0);
901            }
902        }
903
904        let vmo_offset = self.data.payload_region_offset + source_offset as u64;
905        self.supply_pages(&self.cache.delivery_vmo, vmo_offset)
906    }
907}
908
909impl Drop for ChildPagesDelivery<'_> {
910    fn drop(&mut self) {
911        if let Some(status) = self.status.take() {
912            self.cache.report_pager_failure(&self.child_vmo.vmo, self.child_range.clone(), status);
913        }
914    }
915}
916
917impl delivery::DeliveryQueueProvider for Cache {
918    fn deliver_pages(
919        &self,
920        key: u64,
921        target_offset: u64,
922        unverified_pages: UnverifiedPages<'_>,
923    ) -> Result<(), Error> {
924        let cached_blob = self
925            .get_by_key(key)
926            .ok_or_else(|| anyhow!("Unknown or expired key {key} in deliver_pages"))?;
927
928        let page_size = zx::system_get_page_size() as u64;
929        let page_aligned_size = cached_blob.len.div_ceil(page_size) * page_size;
930
931        let verifier = match cached_blob.merkle_verifier.get() {
932            Some(v) => v,
933            None => {
934                // If data arrives before `RegisterBlob` (or if registration failed), the blob has
935                // no cryptographic metadata, so no pages can ever be verified or supplied. Fail the
936                // entire page-aligned range of the VMO with `BAD_STATE` to unblock any waiting
937                // client threads on this blob.
938                self.report_pager_failure(
939                    &cached_blob.vmo,
940                    0..page_aligned_size,
941                    zx::Status::BAD_STATE,
942                );
943                bail!("Data received for uninitialized blob {}", key);
944            }
945        };
946
947        let chunk_len = unverified_pages.len_in_bytes() as u64;
948
949        // `zx_pager_supply_pages` and `zx_pager_op_range(Fail)` require the range to be within the
950        // page-aligned capacity of the VMO. If the range extends past the end of the VMO, the
951        // kernel fails the call with `ZX_ERR_OUT_OF_RANGE`. Clamp the range to the VMO's
952        // page-aligned limit.
953        let bounded_len = std::cmp::min(chunk_len, page_aligned_size.saturating_sub(target_offset));
954        let chunk_range = target_offset..target_offset + bounded_len;
955
956        // For the final chunk of an unaligned blob, the buffer passed to `verify_aligned` is
957        // page-aligned and zero-padded, but `verify_aligned` expects the remaining unaligned length
958        // of the valid data.
959        let remaining_in_blob = cached_blob.len.saturating_sub(target_offset);
960        let unaligned_len = std::cmp::min(chunk_len, remaining_in_blob) as usize;
961        if let Err(e) = verifier.verify_aligned(
962            target_offset as usize,
963            unverified_pages.as_ptr_byte_slice(),
964            unaligned_len,
965        ) {
966            self.report_pager_failure(&cached_blob.vmo, chunk_range, zx::Status::IO_DATA_INTEGRITY);
967            bail!("Failed to verify payload for blob {}: {:?}", key, e);
968        }
969
970        if let Err(e) = self.pager.supply_pages(
971            &cached_blob.vmo,
972            chunk_range.clone(),
973            &self.delivery_vmo,
974            unverified_pages.delivery_offset(),
975        ) {
976            self.report_pager_failure(&cached_blob.vmo, chunk_range, e);
977            bail!("Failed to supply pages: {:?}", e);
978        }
979
980        Ok(())
981    }
982
983    fn register_blob(
984        &self,
985        key: u64,
986        leaf_data: PtrByteSlice<'_>,
987        read_alignment: usize,
988    ) -> Result<(), Error> {
989        if leaf_data.len() % HASH_SIZE != 0 {
990            bail!("RegisterBlob invalid leaf length must be a multiple of HASH_SIZE");
991        }
992
993        let hashes: Vec<Hash> = (0..(leaf_data.len() / HASH_SIZE))
994            .map(|i| {
995                let chunk = leaf_data.subslice(i * HASH_SIZE..(i + 1) * HASH_SIZE);
996                let mut hash_bytes = [0u8; HASH_SIZE];
997                chunk.copy_to_slice(&mut hash_bytes);
998                Hash::from(hash_bytes)
999            })
1000            .collect();
1001
1002        let mut state = self.state.lock();
1003        match state.blob_states_by_key.get_mut(&key) {
1004            Some(BlobState::Opening { root_hash, verifier, .. }) => {
1005                if verifier.is_some() {
1006                    log::warn!(key;
1007                        "RegisterBlob received for blob but it is already initialized"
1008                    );
1009                    return Ok(());
1010                }
1011                *verifier = Some(Self::create_merkle_verifier(
1012                    root_hash,
1013                    key,
1014                    hashes.into_boxed_slice(),
1015                    read_alignment,
1016                )?);
1017                Ok(())
1018            }
1019            Some(BlobState::Ready(cached)) => {
1020                if cached.merkle_verifier.get().is_some() {
1021                    log::warn!(key;
1022                        "RegisterBlob received for blob but it is already initialized"
1023                    );
1024                    return Ok(());
1025                }
1026                let verifier = Self::create_merkle_verifier(
1027                    &cached.root_hash,
1028                    key,
1029                    hashes.into_boxed_slice(),
1030                    read_alignment,
1031                )?;
1032                let _ = cached.merkle_verifier.set(verifier);
1033                Ok(())
1034            }
1035            None => {
1036                bail!("RegisterBlob received for unknown or expired key {key}");
1037            }
1038        }
1039    }
1040
1041    // Verifies the incoming parent chunk and supplies the requested slice to the child VMO.
1042    fn deliver_child_pages(&self, data: delivery::ChildData<'_>) -> Result<(), Error> {
1043        if let Some(delivery) = ChildPagesDelivery::new(self, data)? {
1044            delivery.deliver()?;
1045        }
1046        Ok(())
1047    }
1048
1049    // Supplies an inline-tail page to the child VMO from the parent blob VMO, limiting inline-tail
1050    // processing to at most one delivery worker thread at a time so the remaining workers stay free
1051    // to service incoming parent `Data` commands if reading the parent VMO faults.
1052    fn deliver_child_inline_tail(
1053        &self,
1054        data: delivery::ChildInlineTail,
1055        receiver: &vmo_fifo::Receiver<mapping::RawDeliveryCommand>,
1056    ) -> Result<(), Error> {
1057        {
1058            let mut queue = self.inline_tail_queue.lock();
1059            if queue.busy {
1060                queue.pending.push_back(data);
1061                return Ok(());
1062            }
1063            queue.busy = true;
1064        }
1065
1066        // `process_one_inline_tail` is reentrant and may block on a parent VMO page fault that
1067        // requires another delivery worker thread to process `DeliveryCommand::Data` from
1068        // `receiver`.
1069        receiver.will_maybe_reenter();
1070
1071        let mut current = data;
1072        loop {
1073            if let Err(error) = self.process_one_inline_tail(current) {
1074                log::warn!(error:?; "Failed to process ChildInlineTail");
1075            }
1076            let mut queue = self.inline_tail_queue.lock();
1077            if let Some(next) = queue.pending.pop_front() {
1078                current = next;
1079            } else {
1080                queue.busy = false;
1081                break;
1082            }
1083        }
1084        Ok(())
1085    }
1086}
1087
1088/// Coordinates between FxBlob, the block driver, and the kernel to manage pager-backed blobs such
1089/// that page requests can be handled at the driver instead of the filesystem layer.
1090///
1091/// `BlobPagerAndVerifier` uses a dedicated Pager to create pager-backed VMOs for blobs. Kernel page
1092/// faults on these VMOs are routed directly to the block driver, which reads and decompresses the
1093/// raw data. The driver then passes this data back to `BlobPagerAndVerifier` for cryptographic
1094/// verification and page fault resolution.
1095pub struct BlobPagerAndVerifier {
1096    // Ties the lifetime of the background delivery thread to the `BlobPagerAndVerifier`.
1097    _delivery_processor: delivery::DeliveryQueueProcessor,
1098    mapper_session: fblock::MapperSessionProxy,
1099    cache: Arc<Cache>,
1100    scope: fasync::Scope,
1101}
1102
1103impl BlobPagerAndVerifier {
1104    /// Returns a reference to the shared delivery queue VMO.
1105    pub fn delivery_vmo(&self) -> &zx::Vmo {
1106        self.cache.delivery_vmo()
1107    }
1108
1109    /// Evicts all unreferenced blobs from the internal LRU cache.
1110    pub fn clear_cache(&self) {
1111        let evicted = {
1112            let mut state = self.cache.state.lock();
1113            state.unreferenced_lru.clear();
1114            let unreferenced_keys: Vec<u64> = state
1115                .blob_states_by_key
1116                .iter()
1117                .filter_map(|(&key, blob_state)| match blob_state {
1118                    BlobState::Ready(cached) => {
1119                        if cached.vmo.info().map(|info| info.num_children == 0).unwrap_or(false) {
1120                            Some(key)
1121                        } else {
1122                            None
1123                        }
1124                    }
1125                    BlobState::Opening { .. } => None,
1126                })
1127                .collect();
1128            let mut evicted = Vec::with_capacity(unreferenced_keys.len());
1129            for key in unreferenced_keys {
1130                if let Some(BlobState::Ready(cached)) = state.blob_states_by_key.remove(&key) {
1131                    if let hash_map::Entry::Occupied(entry) =
1132                        state.keys_by_hash.entry(cached.root_hash)
1133                    {
1134                        if *entry.get() == key {
1135                            entry.remove();
1136                        }
1137                    }
1138                    evicted.push(cached);
1139                }
1140            }
1141            evicted
1142        };
1143        drop(evicted);
1144    }
1145
1146    /// Creates a new BlobPagerAndVerifier with the default LRU cache capacity.
1147    ///
1148    /// Establishes a mapping_provider session with FxBlob to receive a shared VMO that will be used
1149    /// to communicate opened blob extent mappings as well as any closed blobs. Then, opens a
1150    /// mapper session with the block driver, forwarding the mapping VMO alongside a Zircon port
1151    /// (for the driver to receive page faults) and a delivery queue VMO (for the driver to emit
1152    /// unverified data for verification).
1153    pub async fn new(
1154        mapping_provider: &fmapping::MappingProviderProxy,
1155        mapper: &fblock::MapperProxy,
1156    ) -> Result<Self, Error> {
1157        Self::new_with_cache_capacity(mapping_provider, mapper, DEFAULT_CACHE_CAPACITY).await
1158    }
1159
1160    /// Creates a new BlobPagerAndVerifier with the specified unreferenced-blob LRU cache capacity.
1161    pub async fn new_with_cache_capacity(
1162        mapping_provider: &fmapping::MappingProviderProxy,
1163        mapper: &fblock::MapperProxy,
1164        cache_capacity: usize,
1165    ) -> Result<Self, Error> {
1166        // Establish a mapping_provider session with Fxfs.
1167        let (mapping_session, session_server) =
1168            fidl::endpoints::create_proxy::<fmapping::MappingSessionMarker>();
1169        let mapping_vmo = mapping_provider
1170            .open_session(session_server)
1171            .await
1172            .context("FIDL error calling MappingProvider.OpenSession")?
1173            .map_err(|e| anyhow!("MappingProvider open_session failed: {e:?}"))?;
1174
1175        // Establish a mapper session with the block driver.
1176        let port = zx::Port::create();
1177        let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE)
1178            .context("Failed to create delivery queue VMO")?;
1179        let delivery_vmo: zx::Vmo = delivery_queue
1180            .duplicate_handle(zx::Rights::SAME_RIGHTS)
1181            .context("Failed to duplicate delivery queue VMO")?;
1182        let delivery_queue_dup = delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
1183        let receiver = vmo_fifo::Receiver::<mapping::RawDeliveryCommand>::new(
1184            delivery_queue,
1185            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1186        )
1187        .context("Failed to create delivery queue receiver")?;
1188        let pager = Arc::new(
1189            zx::Pager::create(zx::PagerOptions::empty()).context("Failed to create pager")?,
1190        );
1191        let port_dup = port.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
1192        let (mapper_session, mapper_session_server) =
1193            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
1194        mapper
1195            .open_session(
1196                mapper_session_server,
1197                mapping_vmo,
1198                Some(port_dup),
1199                Some(delivery_queue_dup),
1200            )
1201            .await
1202            .context("FIDL error calling Mapper.OpenSession")?
1203            .map_err(|e| anyhow!("Mapper.OpenSession failed: {e:?}"))?;
1204
1205        let scope = fasync::Scope::new();
1206        let scope_handle = scope.to_handle();
1207        let cache = Arc::new(Cache::new(
1208            mapping_session,
1209            pager,
1210            port,
1211            delivery_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?,
1212            scope_handle,
1213            cache_capacity,
1214        ));
1215        let _delivery_processor =
1216            delivery::DeliveryQueueProcessor::spawn(receiver, cache.clone(), delivery_vmo)?;
1217
1218        Ok(Self { _delivery_processor, mapper_session, cache, scope })
1219    }
1220
1221    /// Starts recording a profile for `name` and replaying any existing profile of the same name.
1222    pub async fn start_profile(
1223        &self,
1224        name: impl Into<String>,
1225    ) -> Result<ProfileRecording, zx::Status> {
1226        ProfileRecording::start(self.cache.clone(), &self.mapper_session, name.into()).await
1227    }
1228
1229    /// Starts recording and replaying the named profile, stopping and committing the profile in the
1230    /// background after `duration` elapses.
1231    pub async fn record_and_replay_profile(
1232        &self,
1233        name: impl Into<String>,
1234        duration: zx::MonotonicDuration,
1235    ) {
1236        let name = name.into();
1237        let recording = match self.start_profile(name.clone()).await {
1238            Ok(recording) => recording,
1239            Err(status) => {
1240                log::warn!(status:?, name = name.as_str(); "Failed to start profile recording");
1241                return;
1242            }
1243        };
1244        self.scope.spawn(async move {
1245            fasync::Timer::new(duration).await;
1246            if let Err(status) = recording.stop().await {
1247                log::warn!(status:?, name = name.as_str(); "Failed to stop profile recording");
1248            }
1249        });
1250    }
1251
1252    /// Create pager owned VMO for the blob identified by its Merkle Root Hash.
1253    pub async fn create_vmo(&self, identifier: &[u8; 32]) -> Result<zx::Vmo, Error> {
1254        let vmo = self.cache.open_vmo(identifier).await?;
1255        self.cache.record_open(identifier);
1256        Ok(vmo)
1257    }
1258
1259    /// Opens a child `MapperSession` backed by the blob identified by its Merkle root hash.
1260    ///
1261    /// Child VMOs created on `session` are backed by `self.pager` and a dedicated `child_port`
1262    /// passed to the driver. When a child VMO faults, the driver reads the corresponding parent
1263    /// blob chunks and sends `DeliveryCommand::ChildData` over the shared delivery queue so
1264    /// `BlobPagerAndVerifier` can verify the parent data before supplying pages to the child VMO.
1265    pub async fn open_child_session(
1266        &self,
1267        identifier: &[u8; 32],
1268        mapping_vmo: zx::Vmo,
1269        session: fidl::endpoints::ServerEnd<fblock::MapperSessionMarker>,
1270    ) -> Result<(), zx::Status> {
1271        let parent_key = {
1272            let state = self.cache.state.lock();
1273            state.keys_by_hash.get(identifier).copied()
1274        }
1275        .ok_or(zx::Status::NOT_FOUND)?;
1276
1277        let child_port = zx::Port::create();
1278        let child_port_for_driver = child_port.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
1279        let (driver_session_proxy, driver_session_server) =
1280            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
1281
1282        self.mapper_session
1283            .open_child_session(
1284                driver_session_server,
1285                mapping_vmo,
1286                parent_key,
1287                Some(child_port_for_driver),
1288                None,
1289            )
1290            .await
1291            .map_err(|_| zx::Status::PEER_CLOSED)?
1292            .map_err(zx::Status::err_from_raw)?;
1293
1294        let pager = self.cache.pager.clone();
1295        let cache = self.cache.clone();
1296        self.scope.spawn(async move {
1297            let mut stream = session.into_stream();
1298            let mut child_keys = HashSet::new();
1299            while let Ok(Some(request)) = stream.try_next().await {
1300                match request {
1301                    fblock::MapperSessionRequest::CreateVmo { size, options: _, responder } => {
1302                        let key = cache.state.lock().generate_key();
1303                        let res = pager
1304                            .create_vmo(zx::VmoOptions::empty(), &child_port, key, size)
1305                            .and_then(|vmo| {
1306                                let dup = vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
1307                                cache.register_child_vmo(parent_key, key, dup, size);
1308                                child_keys.insert(key);
1309                                Ok((key, vmo))
1310                            });
1311                        let _ = responder.send(res.map_err(zx::Status::into_raw));
1312                    }
1313                    fblock::MapperSessionRequest::CloseVmo { key, .. } => {
1314                        if child_keys.remove(&key) {
1315                            cache.unregister_child_vmo(key);
1316                        }
1317                    }
1318                    fblock::MapperSessionRequest::OpenChildSession { responder, .. } => {
1319                        let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
1320                    }
1321                    fblock::MapperSessionRequest::RecordProfile { responder, .. } => {
1322                        let _ = responder.send(Err(zx::Status::NOT_SUPPORTED.into_raw()));
1323                    }
1324                    fblock::MapperSessionRequest::Close { responder } => {
1325                        let _ = driver_session_proxy.close().await;
1326                        let _ = responder.send(Ok(()));
1327                        break;
1328                    }
1329                    fblock::MapperSessionRequest::_UnknownMethod { .. } => {}
1330                }
1331            }
1332            for child_key in child_keys {
1333                cache.unregister_child_vmo(child_key);
1334            }
1335        });
1336
1337        Ok(())
1338    }
1339
1340    /// Returns a VFS [`vfs::service::Service`] that serves `fuchsia.storage.mapping/FileMapper`
1341    /// by resolving [`vmo_blob::VmoBlob`] tokens in `scope` and opening child mapper sessions on
1342    /// `self`.
1343    pub fn get_file_mapper_handler(
1344        self: &Arc<Self>,
1345        scope: vfs::execution_scope::ExecutionScope,
1346    ) -> Arc<vfs::service::Service> {
1347        let verifier = self.clone();
1348        vfs::service::host(move |mut stream: fmapping::FileMapperRequestStream| {
1349            use futures::TryStreamExt as _;
1350            let verifier = verifier.clone();
1351            let scope = scope.clone();
1352            async move {
1353                while let Some(request) = stream.try_next().await.unwrap_or_else(|error| {
1354                    log::error!(error:?; "error reading FileMapper request");
1355                    None
1356                }) {
1357                    match request {
1358                        fmapping::FileMapperRequest::OpenSession {
1359                            file_token,
1360                            mapping_vmo,
1361                            session,
1362                            responder,
1363                        } => {
1364                            let res = async {
1365                                let (node, rights) = scope
1366                                    .token_registry()
1367                                    .get_owner_and_rights(file_token.into())?
1368                                    .ok_or(zx::Status::NOT_FOUND)?;
1369                                if !rights.contains(fidl_fuchsia_io::Rights::READ_BYTES) {
1370                                    return Err(zx::Status::ACCESS_DENIED);
1371                                }
1372                                let vmo_blob = node
1373                                    .into_any()
1374                                    .downcast::<vmo_blob::VmoBlob>()
1375                                    .map_err(|_| zx::Status::INVALID_ARGS)?;
1376                                verifier
1377                                    .open_child_session(vmo_blob.root_hash(), mapping_vmo, session)
1378                                    .await
1379                            }
1380                            .await;
1381                            let _ = responder.send(res.map_err(zx::Status::into_raw));
1382                        }
1383                        fmapping::FileMapperRequest::_UnknownMethod { .. } => {}
1384                    }
1385                }
1386            }
1387        })
1388    }
1389}
1390
1391#[cfg(test)]
1392mod tests {
1393    use super::*;
1394    use futures::TryStreamExt;
1395    use futures::channel::oneshot;
1396    use mapping::{DeliveryCommand, RawDeliveryCommand};
1397    use test_case::test_case;
1398    use vmo_fifo::SyncSender;
1399
1400    const TARGET_READ_AHEAD_SIZE: usize = mapping::TARGET_READ_AHEAD_SIZE as usize;
1401    const TEST_BLOB_SIZE: u64 = (TARGET_READ_AHEAD_SIZE * 2) as u64;
1402    const TEST_VMO_KEY: u64 = 1;
1403
1404    // Used for testing BlobPagerAndVerifier interactions.
1405    // We arbitrarily allocate a 32KiB buffer so the payload spans multiple VMO pages.
1406    //
1407    // This test environment assumes a single blob file, identified by the `valid_root` hash.
1408    struct TestEnv {
1409        pager_and_verifier: Arc<BlobPagerAndVerifier>,
1410        mapping_task: fasync::Task<()>,
1411        mapper_task: fasync::Task<()>,
1412        close_signal: Option<oneshot::Receiver<()>>,
1413        delivery_vmo: zx::Vmo,
1414        pub valid_root: [u8; 32],
1415        pub valid_leaves: Vec<u8>,
1416        pub blob_data: Vec<u8>,
1417    }
1418
1419    impl TestEnv {
1420        async fn new(blob_size: u64) -> Self {
1421            let blob_data = vec![0x42u8; blob_size as usize];
1422            let (root, leaf_hashes) =
1423                fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
1424            let expected_hash: [u8; 32] = root.into();
1425
1426            let mut flat_leaves = Vec::new();
1427            for hash in &leaf_hashes {
1428                flat_leaves.extend_from_slice(hash.as_bytes());
1429            }
1430
1431            let (mapping_proxy, mut mapping_stream) =
1432                fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1433
1434            let (close_tx, close_rx) = oneshot::channel();
1435            let mapping_task = fasync::Task::spawn(async move {
1436                let mut close_tx = Some(close_tx);
1437                if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1438                    mapping_stream.try_next().await.expect("try_next failed")
1439                {
1440                    let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1441                        .expect("zx::Vmo::create failed");
1442                    responder.send(Ok(mapping_vmo)).expect("send failed");
1443
1444                    let mut session_stream = session.into_stream();
1445                    let mut open_calls = 0;
1446                    while let Some(request) =
1447                        session_stream.try_next().await.expect("try_next failed")
1448                    {
1449                        match request {
1450                            fmapping::MappingSessionRequest::Open {
1451                                key,
1452                                identifier,
1453                                responder,
1454                            } => {
1455                                assert_eq!(identifier, expected_hash);
1456                                assert_eq!(key, TEST_VMO_KEY);
1457                                open_calls += 1;
1458                                assert_eq!(
1459                                    open_calls, 1,
1460                                    "Open should only be called once per identifier"
1461                                );
1462                                responder.send(Ok(blob_size)).expect("send failed");
1463                            }
1464                            fmapping::MappingSessionRequest::Close { key, responder } => {
1465                                assert_eq!(key, TEST_VMO_KEY);
1466                                responder.send(Ok(())).expect("send failed");
1467                                if let Some(tx) = close_tx.take() {
1468                                    let _ = tx.send(());
1469                                }
1470                            }
1471                            _ => {}
1472                        }
1473                    }
1474                }
1475            });
1476
1477            let (mapper_proxy, mut mapper_stream) =
1478                fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1479
1480            let (tx_delivery, rx_delivery) = oneshot::channel();
1481            let mapper_task = fasync::Task::spawn(async move {
1482                if let Some(fblock::MapperRequest::OpenSession {
1483                    delivery_queue, responder, ..
1484                }) = mapper_stream.try_next().await.expect("try_next failed")
1485                {
1486                    tx_delivery.send(delivery_queue).expect("send failed");
1487                    responder.send(Ok(())).expect("send failed");
1488                }
1489            });
1490
1491            Self {
1492                pager_and_verifier: Arc::new(
1493                    BlobPagerAndVerifier::new_with_cache_capacity(&mapping_proxy, &mapper_proxy, 0)
1494                        .await
1495                        .expect("BlobPagerAndVerifier::new failed"),
1496                ),
1497                mapping_task,
1498                mapper_task,
1499                close_signal: Some(close_rx),
1500                delivery_vmo: rx_delivery
1501                    .await
1502                    .expect("rx_delivery wait failed")
1503                    .expect("delivery_vmo was None"),
1504                valid_root: expected_hash,
1505                valid_leaves: flat_leaves,
1506                blob_data,
1507            }
1508        }
1509
1510        async fn teardown(self) {
1511            drop(self.pager_and_verifier);
1512            self.mapping_task.await;
1513            self.mapper_task.await;
1514        }
1515    }
1516
1517    struct ExpectedReadThread {
1518        started_rx: Option<oneshot::Receiver<()>>,
1519        done_rx: oneshot::Receiver<()>,
1520        handle: std::thread::JoinHandle<()>,
1521    }
1522
1523    impl ExpectedReadThread {
1524        async fn wait_started(&mut self) {
1525            if let Some(rx) = self.started_rx.take() {
1526                rx.await.expect("reader thread failed to start");
1527            }
1528        }
1529
1530        async fn wait_and_verify(self) {
1531            self.done_rx.await.expect("reader thread panicked or hung");
1532            self.handle.join().expect("join failed");
1533        }
1534    }
1535
1536    fn spawn_reader_expect_error(
1537        vmo: &zx::Vmo,
1538        offset: u64,
1539        size: usize,
1540        expected_status: zx::Status,
1541    ) -> ExpectedReadThread {
1542        let vmo_clone =
1543            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
1544        let (started_tx, started_rx) = oneshot::channel();
1545        let (done_tx, done_rx) = oneshot::channel();
1546
1547        let handle = std::thread::spawn(move || {
1548            let mut buf = vec![0u8; size];
1549            let _ = started_tx.send(());
1550            let err = vmo_clone.read(&mut buf, offset).expect_err("read should fail");
1551            assert_eq!(err, expected_status);
1552            let _ = done_tx.send(());
1553        });
1554
1555        ExpectedReadThread { started_rx: Some(started_rx), done_rx, handle }
1556    }
1557
1558    #[fuchsia::test]
1559    async fn test_create_vmo() {
1560        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1561        let vmo =
1562            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO");
1563        let size = vmo.get_size().expect("get_size failed");
1564
1565        // Pager VMO sizes are rounded up to the nearest page boundary.
1566        let page_size = zx::system_get_page_size() as u64;
1567        let expected_pages = (TEST_BLOB_SIZE + page_size - 1) / page_size;
1568        assert_eq!(size, expected_pages * page_size);
1569
1570        // Explicitly drop the VMO so `ZX_VMO_ZERO_CHILDREN` fires and the mock mapping
1571        // server receives the `.close()` IPC. Otherwise `teardown` will deadlock waiting
1572        // for the server loop to exit!
1573        drop(vmo);
1574
1575        env.teardown().await;
1576    }
1577
1578    #[fuchsia::test]
1579    async fn test_create_vmo_concurrent_access() {
1580        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1581        let mut futures = vec![];
1582        for _ in 0..10 {
1583            let verifier = env.pager_and_verifier.clone();
1584            futures.push(fasync::Task::spawn(async move {
1585                verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO")
1586            }));
1587        }
1588        let vmos = futures::future::join_all(futures).await;
1589
1590        drop(vmos);
1591        env.teardown().await;
1592    }
1593
1594    #[fuchsia::test]
1595    async fn test_zero_children_eviction() {
1596        let mut env = TestEnv::new(TEST_BLOB_SIZE).await;
1597
1598        let child_vmo =
1599            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1600
1601        {
1602            let state = env.pager_and_verifier.cache.state.lock();
1603            assert_eq!(state.keys_by_hash.get(&env.valid_root), Some(&TEST_VMO_KEY));
1604            assert!(matches!(
1605                state.blob_states_by_key.get(&TEST_VMO_KEY),
1606                Some(BlobState::Ready(_))
1607            ));
1608        }
1609
1610        drop(child_vmo);
1611
1612        // wait for the packet receiver to observe the ZERO_CHILDREN signal and dispatch
1613        // `mapping_session.close()` to the mapping_server.
1614        env.close_signal.take().expect("Missing signal").await.expect("Failed to close");
1615
1616        {
1617            let state = env.pager_and_verifier.cache.state.lock();
1618            assert!(state.keys_by_hash.get(&env.valid_root).is_none());
1619            assert!(state.blob_states_by_key.get(&TEST_VMO_KEY).is_none());
1620        }
1621
1622        env.teardown().await;
1623    }
1624
1625    #[fuchsia::test]
1626    async fn test_create_vmo_multiple_distinct_blobs() {
1627        use futures::StreamExt;
1628
1629        let (mapping_proxy, mut mapping_stream) =
1630            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1631        let (mapper_proxy, mut mapper_stream) =
1632            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1633
1634        let (close_tx, mut close_rx) = futures::channel::mpsc::unbounded();
1635        let mapping_task = fasync::Task::spawn(async move {
1636            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1637                mapping_stream.try_next().await.expect("try_next failed")
1638            {
1639                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1640                    .expect("zx::Vmo::create failed");
1641                responder.send(Ok(mapping_vmo)).expect("send failed");
1642
1643                let mut session_stream = session.into_stream();
1644                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
1645                {
1646                    match request {
1647                        fmapping::MappingSessionRequest::Open { responder, .. } => {
1648                            responder.send(Ok(8192)).expect("send failed");
1649                        }
1650                        fmapping::MappingSessionRequest::Close { key, responder } => {
1651                            responder.send(Ok(())).expect("send failed");
1652                            let _ = close_tx.unbounded_send(key);
1653                        }
1654                        _ => {}
1655                    }
1656                }
1657            }
1658        });
1659
1660        let mapper_task = fasync::Task::spawn(async move {
1661            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1662                mapper_stream.try_next().await.expect("try_next failed")
1663            {
1664                responder.send(Ok(())).expect("send failed");
1665            }
1666        });
1667
1668        let pager_and_verifier = Arc::new(
1669            BlobPagerAndVerifier::new_with_cache_capacity(&mapping_proxy, &mapper_proxy, 0)
1670                .await
1671                .expect("BlobPagerAndVerifier::new failed"),
1672        );
1673
1674        let hash1: [u8; 32] = [0x11; 32];
1675        let hash2: [u8; 32] = [0x22; 32];
1676        let hash3: [u8; 32] = [0x33; 32];
1677
1678        let vmo1 = pager_and_verifier.create_vmo(&hash1).await.expect("create_vmo 1 failed");
1679        assert_eq!(
1680            vmo1.get_name().expect("get_name failed"),
1681            zx::Name::new("blob-11111111").expect("zx::Name::new failed")
1682        );
1683        let vmo2 = pager_and_verifier.create_vmo(&hash2).await.expect("create_vmo 2 failed");
1684        let vmo3 = pager_and_verifier.create_vmo(&hash3).await.expect("create_vmo 3 failed");
1685
1686        let (key1, key2, key3) = {
1687            let state = pager_and_verifier.cache.state.lock();
1688            let k1 = *state.keys_by_hash.get(&hash1).expect("hash1 missing");
1689            let k2 = *state.keys_by_hash.get(&hash2).expect("hash2 missing");
1690            let k3 = *state.keys_by_hash.get(&hash3).expect("hash3 missing");
1691            assert_ne!(k1, k2);
1692            assert_ne!(k2, k3);
1693            assert_ne!(k1, k3);
1694            assert!(matches!(state.blob_states_by_key.get(&k1), Some(BlobState::Ready(_))));
1695            assert!(matches!(state.blob_states_by_key.get(&k2), Some(BlobState::Ready(_))));
1696            assert!(matches!(state.blob_states_by_key.get(&k3), Some(BlobState::Ready(_))));
1697            (k1, k2, k3)
1698        };
1699
1700        // Evict blob 2 by dropping its only child VMO
1701        drop(vmo2);
1702        let closed_key = close_rx.next().await.expect("expected close signal");
1703        assert_eq!(closed_key, key2);
1704
1705        // Verify blob 2 was evicted while blob 1 and 3 remain cached
1706        {
1707            let state = pager_and_verifier.cache.state.lock();
1708            assert!(state.keys_by_hash.get(&hash2).is_none());
1709            assert!(state.blob_states_by_key.get(&key2).is_none());
1710            assert_eq!(state.keys_by_hash.get(&hash1), Some(&key1));
1711            assert_eq!(state.keys_by_hash.get(&hash3), Some(&key3));
1712            assert!(matches!(state.blob_states_by_key.get(&key1), Some(BlobState::Ready(_))));
1713            assert!(matches!(state.blob_states_by_key.get(&key3), Some(BlobState::Ready(_))));
1714        }
1715
1716        // Re-open blob 2: it should generate a new key and open successfully
1717        let vmo2_new = pager_and_verifier.create_vmo(&hash2).await.expect("re-create vmo 2 failed");
1718        {
1719            let state = pager_and_verifier.cache.state.lock();
1720            let k = *state.keys_by_hash.get(&hash2).expect("hash2 missing after re-open");
1721            assert_ne!(k, key2);
1722            assert!(matches!(state.blob_states_by_key.get(&k), Some(BlobState::Ready(_))));
1723        }
1724
1725        drop(vmo1);
1726        drop(vmo3);
1727        drop(vmo2_new);
1728
1729        drop(pager_and_verifier);
1730        mapping_task.await;
1731        mapper_task.await;
1732    }
1733
1734    #[fuchsia::test]
1735    async fn test_lru_cache_retains_and_evicts_blobs() {
1736        use futures::StreamExt;
1737
1738        let (mapping_proxy, mut mapping_stream) =
1739            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1740        let (mapper_proxy, mut mapper_stream) =
1741            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1742
1743        let (close_tx, mut close_rx) = futures::channel::mpsc::unbounded();
1744        let mapping_task = fasync::Task::spawn(async move {
1745            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1746                mapping_stream.try_next().await.expect("try_next failed")
1747            {
1748                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1749                    .expect("zx::Vmo::create failed");
1750                responder.send(Ok(mapping_vmo)).expect("send failed");
1751
1752                let mut session_stream = session.into_stream();
1753                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
1754                {
1755                    match request {
1756                        fmapping::MappingSessionRequest::Open { responder, .. } => {
1757                            responder.send(Ok(8192)).expect("send failed");
1758                        }
1759                        fmapping::MappingSessionRequest::Close { key, responder } => {
1760                            responder.send(Ok(())).expect("send failed");
1761                            let _ = close_tx.unbounded_send(key);
1762                        }
1763                        _ => {}
1764                    }
1765                }
1766            }
1767        });
1768
1769        let mapper_task = fasync::Task::spawn(async move {
1770            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1771                mapper_stream.try_next().await.expect("try_next failed")
1772            {
1773                responder.send(Ok(())).expect("send failed");
1774            }
1775        });
1776
1777        // Create a cache with capacity for 2 unreferenced blobs.
1778        let pager_and_verifier = Arc::new(
1779            BlobPagerAndVerifier::new_with_cache_capacity(&mapping_proxy, &mapper_proxy, 2)
1780                .await
1781                .expect("BlobPagerAndVerifier::new_with_cache_capacity failed"),
1782        );
1783
1784        let hash1: [u8; 32] = [0x11; 32];
1785        let hash2: [u8; 32] = [0x22; 32];
1786        let hash3: [u8; 32] = [0x33; 32];
1787
1788        let vmo1 = pager_and_verifier.create_vmo(&hash1).await.expect("create_vmo 1 failed");
1789        let vmo2 = pager_and_verifier.create_vmo(&hash2).await.expect("create_vmo 2 failed");
1790        let (key1, key2) = {
1791            let state = pager_and_verifier.cache.state.lock();
1792            (*state.keys_by_hash.get(&hash1).unwrap(), *state.keys_by_hash.get(&hash2).unwrap())
1793        };
1794
1795        // Drop both child VMOs: both blobs should enter `unreferenced_lru` without being evicted.
1796        drop(vmo1);
1797        drop(vmo2);
1798        while pager_and_verifier.cache.state.lock().unreferenced_lru.len() < 2 {
1799            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1800        }
1801        assert!(close_rx.try_recv().is_err());
1802
1803        // Re-open blob 1 from the LRU cache: it should reuse `key1` and re-arm zero-children wait.
1804        let vmo1_reopened =
1805            pager_and_verifier.create_vmo(&hash1).await.expect("re-open vmo 1 failed");
1806        assert_eq!(pager_and_verifier.cache.state.lock().keys_by_hash.get(&hash1), Some(&key1));
1807        assert_eq!(pager_and_verifier.cache.state.lock().unreferenced_lru.len(), 1);
1808
1809        // Drop `vmo1_reopened` so `key1` goes to the back of `unreferenced_lru` (after `key2`).
1810        drop(vmo1_reopened);
1811        while pager_and_verifier.cache.state.lock().unreferenced_lru.len() < 2 {
1812            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1813        }
1814
1815        // Open blob 3 and drop its child VMO: `key2` (least recently used) should be evicted.
1816        let vmo3 = pager_and_verifier.create_vmo(&hash3).await.expect("create_vmo 3 failed");
1817        drop(vmo3);
1818        let closed_key = close_rx.next().await.expect("expected close signal for evicted blob 2");
1819        assert_eq!(closed_key, key2);
1820        {
1821            let state = pager_and_verifier.cache.state.lock();
1822            assert!(state.keys_by_hash.get(&hash2).is_none());
1823            assert_eq!(state.keys_by_hash.get(&hash1), Some(&key1));
1824            assert!(state.keys_by_hash.get(&hash3).is_some());
1825        }
1826
1827        drop(pager_and_verifier);
1828        mapping_task.await;
1829        mapper_task.await;
1830    }
1831
1832    #[fuchsia::test]
1833    async fn test_cache_is_cleared_when_create_vmo_fails() {
1834        let (mapping_proxy, mut mapping_stream) =
1835            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1836        let (mapper_proxy, mut mapper_stream) =
1837            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1838
1839        let (mock_opened_tx, mock_opened_rx) = oneshot::channel();
1840        let mapping_task = fasync::Task::spawn(async move {
1841            let mut mock_opened_tx = Some(mock_opened_tx);
1842            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1843                mapping_stream.try_next().await.expect("try_next failed")
1844            {
1845                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1846                    .expect("zx::Vmo::create failed");
1847                responder.send(Ok(mapping_vmo)).expect("send failed");
1848                let mut session_stream = session.into_stream();
1849
1850                if let Some(fmapping::MappingSessionRequest::Open {
1851                    responder: _responder, ..
1852                }) = session_stream.try_next().await.expect("try_next failed")
1853                {
1854                    // Signal the primary test task that the session is inside Open
1855                    if let Some(tx) = mock_opened_tx.take() {
1856                        let _ = tx.send(());
1857                    }
1858
1859                    // Wait for test task to simulate failing create_vmo
1860                    let () = std::future::pending().await;
1861                }
1862            }
1863        });
1864
1865        let mapper_task = fasync::Task::spawn(async move {
1866            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1867                mapper_stream.try_next().await.expect("try_next failed")
1868            {
1869                responder.send(Ok(())).expect("send failed");
1870            }
1871        });
1872
1873        let pager_and_verifer = Arc::new(
1874            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1875                .await
1876                .expect("BlobPagerAndVerifier::new failed"),
1877        );
1878
1879        let blob_data = vec![0x42u8; TEST_BLOB_SIZE as usize];
1880        let (root, _) = fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
1881        let hash_val: [u8; 32] = root.into();
1882
1883        let verifier_clone = pager_and_verifer.clone();
1884
1885        let identifier = hash_val;
1886        let (abortable_future, abort_handle) = futures::future::abortable(async move {
1887            let _ = verifier_clone.create_vmo(&identifier).await;
1888        });
1889
1890        let identifier = &hash_val;
1891        let primary_task = fasync::Task::spawn(abortable_future);
1892
1893        // Wait until the mock mapping session catches the `Open` request and check that the cache
1894        // state is updated.
1895        mock_opened_rx.await.expect("Open exited abruptly");
1896        {
1897            let state = pager_and_verifer.cache.state.lock();
1898            assert_eq!(state.keys_by_hash.get(identifier), Some(&1));
1899            assert!(matches!(state.blob_states_by_key.get(&1), Some(BlobState::Opening { .. })));
1900        }
1901
1902        // Simulate a failed create_vmo (future abort drops execution context)
1903        abort_handle.abort();
1904        let _ = primary_task.await;
1905
1906        // The cache should be cleared after due to PendingEntry being dropped
1907        {
1908            let state = pager_and_verifer.cache.state.lock();
1909            assert!(state.keys_by_hash.get(identifier).is_none());
1910            assert!(state.blob_states_by_key.get(&1).is_none());
1911        }
1912
1913        drop(pager_and_verifer);
1914        drop(mapping_task);
1915        drop(mapper_task);
1916    }
1917
1918    #[fuchsia::test]
1919    async fn test_create_vmo_error_clears_cache_and_unblocks_waiters() {
1920        let (mapping_proxy, mut mapping_stream) =
1921            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1922        let (mapper_proxy, mut mapper_stream) =
1923            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1924
1925        let (open_received_tx, open_received_rx) = oneshot::channel();
1926        let (reply_tx, reply_rx) = oneshot::channel();
1927
1928        let mapping_task = fasync::Task::spawn(async move {
1929            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1930                mapping_stream.try_next().await.expect("try_next failed")
1931            {
1932                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1933                    .expect("zx::Vmo::create failed");
1934                responder.send(Ok(mapping_vmo)).expect("send failed");
1935                let mut session_stream = session.into_stream();
1936
1937                if let Some(fmapping::MappingSessionRequest::Open { responder, .. }) =
1938                    session_stream.try_next().await.expect("try_next failed")
1939                {
1940                    // Signal the test that the primary caller has entered Open and is Pending.
1941                    open_received_tx.send(()).expect("send open_received failed");
1942
1943                    // Await permission to reply with the error.
1944                    reply_rx.await.expect("reply_rx failed");
1945                    responder.send(Err(zx::Status::NOT_FOUND.into_raw())).expect("send failed");
1946                }
1947                // Channel closes when mapping_task finishes, causing subsequent requests to fail.
1948            }
1949        });
1950
1951        let mapper_task = fasync::Task::spawn(async move {
1952            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1953                mapper_stream.try_next().await.expect("try_next failed")
1954            {
1955                responder.send(Ok(())).expect("send failed");
1956            }
1957        });
1958
1959        let pager_and_verifier = Arc::new(
1960            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1961                .await
1962                .expect("BlobPagerAndVerifier::new failed"),
1963        );
1964
1965        let hash: [u8; 32] = [0x55; 32];
1966
1967        // Primary caller starts create_vmo.
1968        let verifier1 = pager_and_verifier.clone();
1969        let primary_future = fasync::Task::spawn(async move { verifier1.create_vmo(&hash).await });
1970
1971        // Wait until the primary caller is handling OpenSession (cache is Pending).
1972        open_received_rx.await.expect("open_received_rx failed");
1973        {
1974            let state = pager_and_verifier.cache.state.lock();
1975            assert!(matches!(
1976                state.keys_by_hash.get(&hash).and_then(|k| state.blob_states_by_key.get(k)),
1977                Some(BlobState::Opening { .. })
1978            ));
1979        }
1980
1981        // A second caller arrives while the primary creation is still pending.
1982        let verifier2 = pager_and_verifier.clone();
1983        let secondary_future =
1984            fasync::Task::spawn(async move { verifier2.create_vmo(&hash).await });
1985
1986        // Allow Fxfs to reply with an error to the primary caller.
1987        reply_tx.send(()).expect("reply_tx send failed");
1988
1989        // Both callers must return an error and complete without hanging on abandoned state, and
1990        // the primary caller must preserve the underlying zx::Status::NOT_FOUND.
1991        let primary_err = primary_future.await.expect_err("expected primary create_vmo to fail");
1992        assert_eq!(primary_err.downcast_ref::<zx::Status>(), Some(&zx::Status::NOT_FOUND));
1993        assert!(secondary_future.await.is_err());
1994
1995        // Cache should be empty.
1996        {
1997            let state = pager_and_verifier.cache.state.lock();
1998            assert!(state.keys_by_hash.get(&hash).is_none());
1999            assert!(state.blob_states_by_key.is_empty());
2000        }
2001
2002        drop(pager_and_verifier);
2003        drop(mapping_task);
2004        drop(mapper_task);
2005    }
2006
2007    #[fuchsia::test]
2008    async fn test_register_blob_arrives_before_open_completes() {
2009        // Test that if `RegisterBlob` arrives while `session.open().await` is still pending
2010        // (the arrival race), the blob's Merkle tree is retained and page reads succeed once
2011        // `create_vmo` completes.
2012        let (mapping_proxy, mut mapping_stream) =
2013            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
2014        let (mapper_proxy, mut mapper_stream) =
2015            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
2016
2017        let blob_data = vec![0x42u8; TEST_BLOB_SIZE as usize];
2018        let (root, leaf_hashes) =
2019            fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
2020        let expected_hash: [u8; 32] = root.into();
2021
2022        let mut flat_leaves = Vec::new();
2023        for hash in &leaf_hashes {
2024            flat_leaves.extend_from_slice(hash.as_bytes());
2025        }
2026
2027        let (tx_delivery, rx_delivery) = oneshot::channel();
2028        let (open_key_tx, open_key_rx) = oneshot::channel();
2029        let (resume_open_tx, resume_open_rx) = oneshot::channel();
2030        let (close_tx, close_rx) = oneshot::channel();
2031
2032        let mapping_task = fasync::Task::spawn(async move {
2033            let mut close_tx = Some(close_tx);
2034            let mut open_key_tx = Some(open_key_tx);
2035            let mut resume_open_rx = Some(resume_open_rx);
2036            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
2037                mapping_stream.try_next().await.expect("try_next failed")
2038            {
2039                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
2040                    .expect("zx::Vmo::create failed");
2041                responder.send(Ok(mapping_vmo)).expect("send failed");
2042
2043                let mut session_stream = session.into_stream();
2044                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
2045                {
2046                    match request {
2047                        fmapping::MappingSessionRequest::Open { key, identifier, responder } => {
2048                            assert_eq!(identifier, expected_hash);
2049                            // Send key to test thread so it can deliver leaves before Open returns
2050                            if let Some(tx) = open_key_tx.take() {
2051                                tx.send(key).expect("open_key_tx failed");
2052                            }
2053                            if let Some(rx) = resume_open_rx.take() {
2054                                rx.await.expect("resume_open_rx failed");
2055                            }
2056                            responder.send(Ok(TEST_BLOB_SIZE)).expect("send failed");
2057                        }
2058                        fmapping::MappingSessionRequest::Close { key: _, responder } => {
2059                            responder.send(Ok(())).expect("send failed");
2060                            if let Some(tx) = close_tx.take() {
2061                                let _ = tx.send(());
2062                            }
2063                        }
2064                        _ => {}
2065                    }
2066                }
2067            }
2068        });
2069
2070        let mapper_task = fasync::Task::spawn(async move {
2071            if let Some(fblock::MapperRequest::OpenSession { delivery_queue, responder, .. }) =
2072                mapper_stream.try_next().await.expect("try_next failed")
2073            {
2074                tx_delivery.send(delivery_queue).expect("send failed");
2075                responder.send(Ok(())).expect("send failed");
2076            }
2077        });
2078
2079        let pager_and_verifier = Arc::new(
2080            BlobPagerAndVerifier::new_with_cache_capacity(&mapping_proxy, &mapper_proxy, 0)
2081                .await
2082                .expect("BlobPagerAndVerifier::new failed"),
2083        );
2084
2085        let delivery_vmo =
2086            rx_delivery.await.expect("rx_delivery wait failed").expect("delivery_vmo was None");
2087        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2088            delivery_vmo
2089                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2090                .expect("duplicate_handle failed"),
2091            zx::system_get_page_size() as usize,
2092            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2093        )
2094        .expect("SyncSender::new failed");
2095
2096        let verifier_clone = pager_and_verifier.clone();
2097        let create_task =
2098            fasync::Task::spawn(async move { verifier_clone.create_vmo(&expected_hash).await });
2099
2100        // Wait until `open()` is called and get the client-allocated key
2101        let key = open_key_rx.await.expect("open_key_rx failed");
2102
2103        // While `open()` is still pending, send `RegisterBlob`
2104        let mut payload =
2105            sender.reserve_payload(flat_leaves.len()).expect("reserve_payload failed");
2106        payload.data().copy_from_slice(&flat_leaves);
2107        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2108            key: key as u64,
2109            offset: payload.offset(),
2110            length: flat_leaves.len() as u32,
2111            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2112        }
2113        .into();
2114        payload.commit(raw_cmd).expect("commit failed");
2115
2116        // Wait until delivery thread processes `RegisterBlob` on the pending entry
2117        while !pager_and_verifier.cache.is_merkle_initialized(key) {
2118            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2119        }
2120
2121        // Resume Open and wait for `create_vmo` to finish
2122        resume_open_tx.send(()).expect("resume_open_tx failed");
2123        let vmo = create_task.await.expect("create_vmo failed");
2124
2125        // We should be able to deliver data and read it immediately
2126        let chunk = &blob_data[..TARGET_READ_AHEAD_SIZE];
2127        let mut data_payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
2128        data_payload.data().copy_from_slice(chunk);
2129        let data_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2130            key: key as u64,
2131            offset: data_payload.offset(),
2132            length: chunk.len() as u32,
2133            target_offset: 0,
2134        }
2135        .into();
2136        data_payload.commit(data_cmd).expect("commit failed");
2137
2138        let mut read_buf = vec![0u8; TARGET_READ_AHEAD_SIZE];
2139        vmo.read(&mut read_buf, 0).expect("read failed");
2140        assert_eq!(read_buf, chunk);
2141
2142        drop(vmo);
2143        close_rx.await.expect("close_rx failed");
2144
2145        drop(pager_and_verifier);
2146        mapping_task.await;
2147        mapper_task.await;
2148    }
2149
2150    #[fuchsia::test]
2151    async fn test_delivery_register_blob_verification() {
2152        let env = TestEnv::new(TEST_BLOB_SIZE).await;
2153
2154        let _paged_vmo =
2155            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2156
2157        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2158            env.delivery_vmo
2159                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2160                .expect("duplicate_handle failed"),
2161            zx::system_get_page_size() as usize, // alignment of payload
2162            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2163        )
2164        .expect("SyncSender::new failed");
2165
2166        // Test corrupted leaves should fail register blob
2167        let mut corrupted_leaves = env.valid_leaves.clone();
2168        corrupted_leaves[0] ^= 0xFF;
2169
2170        let mut bad_payload =
2171            sender.reserve_payload(corrupted_leaves.len()).expect("reserve_payload failed");
2172        bad_payload.data().copy_from_slice(&corrupted_leaves);
2173
2174        let bad_raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2175            key: TEST_VMO_KEY,
2176            offset: bad_payload.offset(),
2177            length: corrupted_leaves.len() as u32,
2178            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2179        }
2180        .into();
2181        bad_payload.commit(bad_raw_cmd).expect("commit failed");
2182
2183        // Yield to allow background thread to process the invalid merkle
2184        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2185
2186        let blob =
2187            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2188
2189        // merkle_verifier should remain as None as the leaves are corrupted
2190        assert!(blob.merkle_verifier.get().is_none());
2191
2192        // Test with valid leaves
2193        let mut payload =
2194            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2195        payload.data().copy_from_slice(&env.valid_leaves);
2196
2197        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2198            key: TEST_VMO_KEY,
2199            offset: payload.offset(),
2200            length: env.valid_leaves.len() as u32,
2201            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2202        }
2203        .into();
2204        payload.commit(raw_cmd).expect("commit failed");
2205
2206        while blob.merkle_verifier.get().is_none() {
2207            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2208        }
2209
2210        drop(_paged_vmo);
2211        env.teardown().await;
2212    }
2213
2214    #[fuchsia::test]
2215    async fn test_register_blob_invalid_commands() {
2216        let env = TestEnv::new(TEST_BLOB_SIZE).await;
2217
2218        let _paged_vmo =
2219            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2220
2221        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2222            env.delivery_vmo
2223                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2224                .expect("duplicate_handle failed"),
2225            std::mem::align_of::<RawDeliveryCommand>(),
2226            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2227        )
2228        .expect("SyncSender::new failed");
2229
2230        let blob =
2231            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2232
2233        // Test with unknown/expired key
2234        let mut invalid_key_payload =
2235            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2236        invalid_key_payload.data().copy_from_slice(&env.valid_leaves);
2237        let invalid_key_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2238            key: 9999 as u64,
2239            offset: invalid_key_payload.offset(),
2240            length: env.valid_leaves.len() as u32,
2241            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2242        }
2243        .into();
2244        invalid_key_payload.commit(invalid_key_cmd).expect("commit failed");
2245        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2246        assert!(blob.merkle_verifier.get().is_none());
2247
2248        // Test out-of-bounds offset/length
2249        let oob_payload = sender.reserve_payload(8).expect("reserve_payload failed");
2250        let oob_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2251            key: TEST_VMO_KEY,
2252            offset: u32::MAX - 4, // Malicious offset
2253            length: 8,
2254            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2255        }
2256        .into();
2257        oob_payload.commit(oob_cmd).expect("commit failed");
2258        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2259        assert!(blob.merkle_verifier.get().is_none());
2260
2261        // Test invalid leaf length (not a multiple of HASH_SIZE)
2262        let invalid_len_payload = sender.reserve_payload(10).expect("reserve_payload failed");
2263        let invalid_len_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2264            key: TEST_VMO_KEY,
2265            offset: invalid_len_payload.offset(),
2266            length: 10,
2267            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2268        }
2269        .into();
2270        invalid_len_payload.commit(invalid_len_cmd).expect("commit failed");
2271        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2272        assert!(blob.merkle_verifier.get().is_none());
2273
2274        drop(_paged_vmo);
2275        env.teardown().await;
2276    }
2277
2278    #[fuchsia::test]
2279    async fn test_delivery_data_supplies_pages() {
2280        let env = TestEnv::new(TEST_BLOB_SIZE).await;
2281
2282        let paged_vmo =
2283            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2284
2285        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2286            env.delivery_vmo
2287                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2288                .expect("duplicate_handle failed"),
2289            zx::system_get_page_size() as usize, // alignment of payload
2290            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2291        )
2292        .expect("SyncSender::new failed");
2293
2294        let mut payload =
2295            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2296        payload.data().copy_from_slice(&env.valid_leaves);
2297        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2298            key: TEST_VMO_KEY,
2299            offset: payload.offset(),
2300            length: env.valid_leaves.len() as u32,
2301            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2302        }
2303        .into();
2304        payload.commit(raw_cmd).expect("commit failed");
2305
2306        let blob =
2307            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2308        while blob.merkle_verifier.get().is_none() {
2309            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2310        }
2311
2312        let test_payload = &env.blob_data[..TARGET_READ_AHEAD_SIZE];
2313
2314        let mut payload =
2315            sender.reserve_payload(test_payload.len()).expect("reserve_payload failed");
2316        payload.data().copy_from_slice(&test_payload);
2317
2318        let cmd: RawDeliveryCommand = DeliveryCommand::Data {
2319            key: TEST_VMO_KEY,
2320            target_offset: 0,
2321            length: test_payload.len() as u32,
2322            offset: payload.offset(),
2323        }
2324        .into();
2325        payload.commit(cmd).expect("commit failed");
2326
2327        let mut read_buf = vec![0u8; TARGET_READ_AHEAD_SIZE];
2328        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo failed");
2329        assert_eq!(read_buf, test_payload);
2330
2331        drop(paged_vmo);
2332        env.teardown().await;
2333    }
2334
2335    #[fuchsia::test]
2336    async fn test_delivery_data_corrupted() {
2337        // Verify that when a delivered chunk fails verification, client threads waiting
2338        // for data within that chunk receive `ZX_ERR_IO_DATA_INTEGRITY`.
2339        let blob_size = TARGET_READ_AHEAD_SIZE as u64;
2340        let env = TestEnv::new(blob_size).await;
2341
2342        let paged_vmo =
2343            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2344
2345        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2346            env.delivery_vmo
2347                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2348                .expect("duplicate_handle failed"),
2349            zx::system_get_page_size() as usize,
2350            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2351        )
2352        .expect("SyncSender::new failed");
2353
2354        let mut payload =
2355            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2356        payload.data().copy_from_slice(&env.valid_leaves);
2357        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2358            key: TEST_VMO_KEY,
2359            offset: payload.offset(),
2360            length: env.valid_leaves.len() as u32,
2361            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2362        }
2363        .into();
2364        payload.commit(raw_cmd).expect("commit failed");
2365
2366        let blob =
2367            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2368        while blob.merkle_verifier.get().is_none() {
2369            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2370        }
2371
2372        // Prepare a corrupted 128 KiB chunk (the entire blob data).
2373        let mut corrupted_data = env.blob_data.clone();
2374        corrupted_data[0] ^= 0xFF;
2375
2376        let mut payload =
2377            sender.reserve_payload(corrupted_data.len()).expect("reserve_payload failed");
2378        payload.data().copy_from_slice(&corrupted_data);
2379        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2380            key: TEST_VMO_KEY,
2381            offset: payload.offset(),
2382            length: corrupted_data.len() as u32,
2383            target_offset: 0,
2384        }
2385        .into();
2386
2387        // Start client threads reading different pages within the delivery data chunk.
2388        // When verification fails, all waiting threads must fail with `IO_DATA_INTEGRITY`.
2389        let page_size = zx::system_get_page_size() as usize;
2390        let mut reader1 =
2391            spawn_reader_expect_error(&paged_vmo, 0, page_size, zx::Status::IO_DATA_INTEGRITY);
2392        let mut reader2 = spawn_reader_expect_error(
2393            &paged_vmo,
2394            page_size as u64,
2395            page_size,
2396            zx::Status::IO_DATA_INTEGRITY,
2397        );
2398
2399        // Wait for both client threads to start executing before giving them time to block on their
2400        // page faults.
2401        reader1.wait_started().await;
2402        reader2.wait_started().await;
2403        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2404
2405        // Commit the corrupt chunk. Verification will fail, causing the blocked `vmo.read()`
2406        // calls for that chunk to return `IO_DATA_INTEGRITY`.
2407        payload.commit(raw_cmd).expect("commit failed");
2408
2409        reader1.wait_and_verify().await;
2410        reader2.wait_and_verify().await;
2411
2412        drop(paged_vmo);
2413        env.teardown().await;
2414    }
2415
2416    #[fuchsia::test]
2417    async fn test_delivery_data_corrupted_chunk_preserves_supplied_pages() {
2418        let read_alignment = TARGET_READ_AHEAD_SIZE;
2419        let blob_size = (read_alignment * 2) as u64;
2420        let env = TestEnv::new(blob_size).await;
2421
2422        let paged_vmo =
2423            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2424
2425        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2426            env.delivery_vmo
2427                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2428                .expect("duplicate_handle failed"),
2429            zx::system_get_page_size() as usize,
2430            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2431        )
2432        .expect("SyncSender::new failed");
2433
2434        let mut payload =
2435            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2436        payload.data().copy_from_slice(&env.valid_leaves);
2437        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2438            key: TEST_VMO_KEY,
2439            offset: payload.offset(),
2440            length: env.valid_leaves.len() as u32,
2441            read_alignment: read_alignment as u32,
2442        }
2443        .into();
2444        payload.commit(raw_cmd).expect("commit failed");
2445
2446        let blob =
2447            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2448        while blob.merkle_verifier.get().is_none() {
2449            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2450        }
2451
2452        let page_size = zx::system_get_page_size() as usize;
2453
2454        // Chunk 1 test for successful page request.
2455        // Start a reader on page 0 and deliver the valid first chunk (0..read_alignment).
2456        let paged_vmo_clone1 =
2457            paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
2458        let (tx1, rx1) = oneshot::channel();
2459        let expected_page0 = env.blob_data[..page_size].to_vec();
2460        let thread1 = std::thread::spawn(move || {
2461            let mut buf = vec![0u8; page_size];
2462            paged_vmo_clone1.read(&mut buf, 0).expect("read should succeed");
2463            assert_eq!(buf, expected_page0);
2464            let _ = tx1.send(());
2465        });
2466        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2467
2468        let valid_chunk = &env.blob_data[..read_alignment];
2469        let mut payload =
2470            sender.reserve_payload(valid_chunk.len()).expect("reserve_payload failed");
2471        payload.data().copy_from_slice(valid_chunk);
2472        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2473            key: TEST_VMO_KEY,
2474            offset: payload.offset(),
2475            length: valid_chunk.len() as u32,
2476            target_offset: 0,
2477        }
2478        .into();
2479        payload.commit(raw_cmd).expect("commit failed");
2480
2481        rx1.await.expect("thread1 panicked or hung");
2482        thread1.join().expect("join failed");
2483
2484        // Test for failed page requests on the subsequent chunk.
2485        let mut reader2 = spawn_reader_expect_error(
2486            &paged_vmo,
2487            read_alignment as u64,
2488            page_size,
2489            zx::Status::IO_DATA_INTEGRITY,
2490        );
2491        reader2.wait_started().await;
2492        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2493
2494        // Deliver corrupted second chunk (read_alignment..read_alignment * 2).
2495        let mut corrupted_chunk = env.blob_data[read_alignment..].to_vec();
2496        corrupted_chunk[0] ^= 0xFF;
2497
2498        let mut payload =
2499            sender.reserve_payload(corrupted_chunk.len()).expect("reserve_payload failed");
2500        payload.data().copy_from_slice(&corrupted_chunk);
2501        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2502            key: TEST_VMO_KEY,
2503            offset: payload.offset(),
2504            length: corrupted_chunk.len() as u32,
2505            target_offset: read_alignment as u64,
2506        }
2507        .into();
2508        payload.commit(raw_cmd).expect("commit failed");
2509
2510        reader2.wait_and_verify().await;
2511
2512        // Verify that the previously supplied page 0 remains intact and readable.
2513        let mut buf = vec![0u8; page_size];
2514        paged_vmo.read(&mut buf, 0).expect("previously supplied page 0 must still be readable");
2515        assert_eq!(buf, env.blob_data[..page_size]);
2516
2517        drop(paged_vmo);
2518        env.teardown().await;
2519    }
2520
2521    #[fuchsia::test]
2522    async fn test_delivery_data_corrupted_multi_chunk_read() {
2523        let read_alignment = TARGET_READ_AHEAD_SIZE;
2524        let blob_size = (read_alignment * 3) as u64;
2525        let env = TestEnv::new(blob_size).await;
2526
2527        let paged_vmo =
2528            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2529
2530        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2531            env.delivery_vmo
2532                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2533                .expect("duplicate_handle failed"),
2534            zx::system_get_page_size() as usize,
2535            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2536        )
2537        .expect("SyncSender::new failed");
2538
2539        let mut payload =
2540            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2541        payload.data().copy_from_slice(&env.valid_leaves);
2542        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2543            key: TEST_VMO_KEY,
2544            offset: payload.offset(),
2545            length: env.valid_leaves.len() as u32,
2546            read_alignment: read_alignment as u32,
2547        }
2548        .into();
2549        payload.commit(raw_cmd).expect("commit failed");
2550
2551        let blob =
2552            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2553        while blob.merkle_verifier.get().is_none() {
2554            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2555        }
2556
2557        // A 384 KiB `vmo.read()` triggers a single page request spanning multiple pages.
2558        // Chunk 1 (0..128 KiB) is valid, Chunk 2 (128..256 KiB) is corrupted, and Chunk 3 is not
2559        // delivered because delivery stops on verification failure.
2560        // Verify that when Chunk 2 fails verification, the page request fails and
2561        // `vmo.read()` returns `IO_DATA_INTEGRITY`.
2562        let mut reader = spawn_reader_expect_error(
2563            &paged_vmo,
2564            0,
2565            blob_size as usize,
2566            zx::Status::IO_DATA_INTEGRITY,
2567        );
2568        reader.wait_started().await;
2569        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2570
2571        // Deliver Chunk 1 (0..128 KiB) valid.
2572        let valid_chunk1 = &env.blob_data[..read_alignment];
2573        let mut payload =
2574            sender.reserve_payload(valid_chunk1.len()).expect("reserve_payload failed");
2575        payload.data().copy_from_slice(valid_chunk1);
2576        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2577            key: TEST_VMO_KEY,
2578            offset: payload.offset(),
2579            length: valid_chunk1.len() as u32,
2580            target_offset: 0,
2581        }
2582        .into();
2583        payload.commit(raw_cmd).expect("commit failed");
2584
2585        // Allow the reader thread time to receive the supplied first chunk, copy it, and block on
2586        // the second chunk's unpopulated pages.
2587        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2588
2589        // Deliver Chunk 2 (128..256 KiB) corrupted.
2590        let mut corrupted_chunk2 = env.blob_data[read_alignment..read_alignment * 2].to_vec();
2591        corrupted_chunk2[0] ^= 0xFF;
2592        let mut payload =
2593            sender.reserve_payload(corrupted_chunk2.len()).expect("reserve_payload failed");
2594        payload.data().copy_from_slice(&corrupted_chunk2);
2595        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2596            key: TEST_VMO_KEY,
2597            offset: payload.offset(),
2598            length: corrupted_chunk2.len() as u32,
2599            target_offset: read_alignment as u64,
2600        }
2601        .into();
2602        payload.commit(raw_cmd).expect("commit failed");
2603
2604        // Don't deliver chunk 3 since chunk 2 failed.
2605
2606        // The `vmo.read()` must fail with IO_DATA_INTEGRITY.
2607        reader.wait_and_verify().await;
2608
2609        drop(paged_vmo);
2610        env.teardown().await;
2611    }
2612
2613    #[fuchsia::test]
2614    async fn test_delivery_data_uninitialized() {
2615        let env = TestEnv::new(TEST_BLOB_SIZE).await;
2616
2617        let paged_vmo =
2618            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2619
2620        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2621            env.delivery_vmo
2622                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2623                .expect("duplicate_handle failed"),
2624            zx::system_get_page_size() as usize,
2625            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2626        )
2627        .expect("SyncSender::new failed");
2628
2629        // Spawn reader thread to generate the initial page request
2630        let mut reader = spawn_reader_expect_error(&paged_vmo, 0, 8192, zx::Status::BAD_STATE);
2631        reader.wait_started().await;
2632        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2633
2634        // Intentionally push a Data chunk without sending RegisterBlob first
2635        let chunk = &env.blob_data[..8192];
2636        let mut payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
2637        payload.data().copy_from_slice(chunk);
2638        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2639            key: TEST_VMO_KEY,
2640            offset: payload.offset(),
2641            length: chunk.len() as u32,
2642            target_offset: 0,
2643        }
2644        .into();
2645        payload.commit(raw_cmd).expect("commit failed");
2646
2647        reader.wait_and_verify().await;
2648
2649        drop(paged_vmo);
2650        env.teardown().await;
2651    }
2652
2653    #[test_case(TARGET_READ_AHEAD_SIZE; "read_alignment_128k")]
2654    #[test_case(96 * 1024; "read_alignment_96k")]
2655    #[fuchsia::test]
2656    async fn test_delivery_data_multiple_chunks(read_alignment: usize) {
2657        let env = TestEnv::new(TEST_BLOB_SIZE).await;
2658
2659        let paged_vmo =
2660            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2661
2662        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2663            env.delivery_vmo
2664                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2665                .expect("duplicate_handle failed"),
2666            zx::system_get_page_size() as usize,
2667            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2668        )
2669        .expect("SyncSender::new failed");
2670
2671        let mut payload =
2672            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2673        payload.data().copy_from_slice(&env.valid_leaves);
2674        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2675            key: TEST_VMO_KEY,
2676            offset: payload.offset(),
2677            length: env.valid_leaves.len() as u32,
2678            read_alignment: read_alignment as u32,
2679        }
2680        .into();
2681        payload.commit(raw_cmd).expect("commit failed");
2682
2683        let blob =
2684            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2685        while blob.merkle_verifier.get().is_none() {
2686            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2687        }
2688
2689        let expected_data = env.blob_data.clone();
2690        let paged_vmo_clone =
2691            paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
2692
2693        let (tx, rx) = oneshot::channel();
2694        let thread = std::thread::spawn(move || {
2695            let mut buf = vec![0u8; expected_data.len()];
2696            paged_vmo_clone.read(&mut buf, 0).expect("failed to read from paged vmo");
2697            assert_eq!(buf, expected_data);
2698            let _ = tx.send(());
2699        });
2700
2701        // Push incrementally - chunks must be a multiple of the blob's read_alignment
2702        for (i, chunk) in env.blob_data.chunks(read_alignment).enumerate() {
2703            let mut payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
2704            payload.data().copy_from_slice(chunk);
2705            let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2706                key: TEST_VMO_KEY,
2707                offset: payload.offset(),
2708                length: chunk.len() as u32,
2709                target_offset: (i * read_alignment) as u64,
2710            }
2711            .into();
2712            payload.commit(raw_cmd).expect("commit failed");
2713        }
2714
2715        rx.await.expect("reading thread panicked or hung");
2716        thread.join().expect("join failed");
2717
2718        drop(paged_vmo);
2719        env.teardown().await;
2720    }
2721
2722    #[fuchsia::test]
2723    async fn test_delivery_data_unaligned_blob_size() {
2724        let data_size = (TARGET_READ_AHEAD_SIZE + 1024) as u64; // Blob with unaligned size
2725        let env = TestEnv::new(data_size).await;
2726
2727        let paged_vmo =
2728            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO");
2729
2730        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2731            env.delivery_vmo
2732                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2733                .expect("duplicate_handle failed"),
2734            zx::system_get_page_size() as usize, // alignment of payload
2735            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2736        )
2737        .expect("SyncSender::new failed");
2738
2739        let mut payload =
2740            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2741        payload.data().copy_from_slice(&env.valid_leaves);
2742        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2743            key: TEST_VMO_KEY,
2744            offset: payload.offset(),
2745            length: env.valid_leaves.len() as u32,
2746            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2747        }
2748        .into();
2749        payload.commit(raw_cmd).expect("commit failed");
2750
2751        let blob =
2752            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2753        while blob.merkle_verifier.get().is_none() {
2754            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2755        }
2756
2757        let test_payload = env.blob_data.clone();
2758        assert_eq!(test_payload.len(), data_size as usize);
2759
2760        let chunk1 = &test_payload[..TARGET_READ_AHEAD_SIZE];
2761        let mut payload1 = sender.reserve_payload(chunk1.len()).expect("reserve_payload failed");
2762        payload1.data().copy_from_slice(chunk1);
2763        let off1 = payload1.offset();
2764        payload1
2765            .commit(
2766                DeliveryCommand::Data {
2767                    key: TEST_VMO_KEY,
2768                    target_offset: 0,
2769                    length: chunk1.len() as u32,
2770                    offset: off1,
2771                }
2772                .into(),
2773            )
2774            .expect("commit failed");
2775
2776        let chunk2 = &test_payload[TARGET_READ_AHEAD_SIZE..];
2777        let page_size = zx::system_get_page_size() as usize;
2778        let chunk2_len_aligned = chunk2.len().div_ceil(page_size) * page_size;
2779        let mut payload2 =
2780            sender.reserve_payload(chunk2_len_aligned).expect("reserve_payload failed");
2781        let payload2_data = payload2.data();
2782        payload2_data.subslice_mut(0..chunk2.len()).copy_from_slice(chunk2);
2783        payload2_data.subslice_mut(chunk2.len()..chunk2_len_aligned).fill(0);
2784
2785        let off2 = payload2.offset();
2786        payload2
2787            .commit(
2788                DeliveryCommand::Data {
2789                    key: TEST_VMO_KEY,
2790                    target_offset: TARGET_READ_AHEAD_SIZE as u64,
2791                    length: chunk2_len_aligned as u32,
2792                    offset: off2,
2793                }
2794                .into(),
2795            )
2796            .expect("commit failed");
2797
2798        let mut read_buf = vec![0u8; data_size as usize];
2799        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo failed");
2800        assert_eq!(read_buf, test_payload);
2801
2802        let page_size = zx::system_get_page_size() as u64;
2803        // Round the unaligned data size to the next page boundary
2804        let out_of_bounds_offset = data_size.div_ceil(page_size) * page_size;
2805        // Attempt to read from an out of bounds offset
2806        let mut read_buf = vec![0u8; 1];
2807        let res = paged_vmo.read(&mut read_buf, out_of_bounds_offset);
2808        assert_eq!(res, Err(zx::Status::OUT_OF_RANGE));
2809
2810        drop(paged_vmo);
2811        env.teardown().await;
2812    }
2813
2814    #[fuchsia::test]
2815    async fn test_open_child_session() {
2816        let (mapping_proxy, mut mapping_stream) =
2817            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
2818        let (mapper_proxy, mut mapper_stream) =
2819            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
2820
2821        let _mapping_task = fasync::Task::spawn(async move {
2822            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
2823                mapping_stream.try_next().await.expect("try_next failed")
2824            {
2825                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
2826                    .expect("zx::Vmo::create failed");
2827                responder.send(Ok(mapping_vmo)).expect("send failed");
2828
2829                let mut session_stream = session.into_stream();
2830                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
2831                {
2832                    match request {
2833                        fmapping::MappingSessionRequest::Open { responder, .. } => {
2834                            responder.send(Ok(8192)).expect("send failed");
2835                        }
2836                        fmapping::MappingSessionRequest::Close { responder, .. } => {
2837                            responder.send(Ok(())).expect("send failed");
2838                        }
2839                        _ => {}
2840                    }
2841                }
2842            }
2843        });
2844
2845        let (child_key_tx, child_key_rx) = oneshot::channel();
2846        let _mapper_task = fasync::Task::spawn(async move {
2847            if let Some(fblock::MapperRequest::OpenSession { session, responder, .. }) =
2848                mapper_stream.try_next().await.expect("try_next failed")
2849            {
2850                responder.send(Ok(())).expect("send failed");
2851                let mut session_stream = session.into_stream();
2852                if let Some(fblock::MapperSessionRequest::OpenChildSession {
2853                    parent_key,
2854                    port,
2855                    delivery_queue,
2856                    responder,
2857                    ..
2858                }) = session_stream.try_next().await.expect("try_next failed")
2859                {
2860                    assert!(port.is_some());
2861                    assert!(delivery_queue.is_none());
2862                    child_key_tx.send(parent_key).expect("send failed");
2863                    responder.send(Ok(())).expect("send failed");
2864                }
2865            }
2866        });
2867
2868        let pager_and_verifier = Arc::new(
2869            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
2870                .await
2871                .expect("BlobPagerAndVerifier::new failed"),
2872        );
2873
2874        let hash: [u8; 32] = [0x55; 32];
2875        let unknown_hash: [u8; 32] = [0x99; 32];
2876
2877        // Unknown blob should return NOT_FOUND.
2878        let (_client, server) = fidl::endpoints::create_endpoints::<fblock::MapperSessionMarker>();
2879        let mapping_vmo = zx::Vmo::create(4096).unwrap();
2880        assert_eq!(
2881            pager_and_verifier.open_child_session(&unknown_hash, mapping_vmo, server).await,
2882            Err(zx::Status::NOT_FOUND)
2883        );
2884
2885        // Open the blob first via create_vmo.
2886        let vmo = pager_and_verifier.create_vmo(&hash).await.expect("create_vmo failed");
2887        let (client_proxy, server) = fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
2888        let mapping_vmo = zx::Vmo::create(4096).unwrap();
2889        pager_and_verifier
2890            .open_child_session(&hash, mapping_vmo, server)
2891            .await
2892            .expect("open_child_session failed");
2893
2894        let parent_key = child_key_rx.await.expect("failed to receive parent_key");
2895        assert_eq!(parent_key, TEST_VMO_KEY);
2896
2897        let (child_key, _child_vmo) = client_proxy
2898            .create_vmo(4096, fblock::CreateVmoOptions::empty())
2899            .await
2900            .expect("create_vmo fidl failed")
2901            .expect("create_vmo returned error");
2902        assert!(pager_and_verifier.cache.get_child_vmo(child_key).is_some());
2903
2904        client_proxy.close_vmo(child_key).expect("close_vmo fidl failed");
2905        while pager_and_verifier.cache.get_child_vmo(child_key).is_some() {
2906            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2907        }
2908        drop(vmo);
2909    }
2910
2911    #[fuchsia::test]
2912    async fn test_deliver_child_pages() {
2913        let env = TestEnv::new(32 * 1024).await;
2914        let _parent_vmo =
2915            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2916
2917        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2918            env.delivery_vmo
2919                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2920                .expect("duplicate_handle failed"),
2921            zx::system_get_page_size() as usize,
2922            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2923        )
2924        .expect("SyncSender::new failed");
2925
2926        // Register parent blob with 32 KiB verification alignment.
2927        let mut payload =
2928            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2929        payload.data().copy_from_slice(&env.valid_leaves);
2930        let off = payload.offset();
2931        payload
2932            .commit(
2933                DeliveryCommand::RegisterBlob {
2934                    key: TEST_VMO_KEY,
2935                    offset: off,
2936                    length: env.valid_leaves.len() as u32,
2937                    read_alignment: 32 * 1024,
2938                }
2939                .into(),
2940            )
2941            .expect("commit failed");
2942
2943        let blob =
2944            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2945        while blob.merkle_verifier.get().is_none() {
2946            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2947        }
2948
2949        let child_port = zx::Port::create();
2950        let child_key = 77u64;
2951        let child_vmo = env
2952            .pager_and_verifier
2953            .cache
2954            .pager
2955            .create_vmo(zx::VmoOptions::empty(), &child_port, child_key, 100)
2956            .unwrap();
2957        env.pager_and_verifier.cache.register_child_vmo(
2958            TEST_VMO_KEY,
2959            child_key,
2960            child_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
2961            100,
2962        );
2963
2964        let mut payload = sender.reserve_payload(32 * 1024).expect("reserve_payload failed");
2965        payload.data().copy_from_slice(&env.blob_data);
2966        let off = payload.offset();
2967        payload
2968            .commit(
2969                DeliveryCommand::ChildData {
2970                    parent_offset: 0,
2971                    offset: off,
2972                    length: 32 * 1024,
2973                    child_key,
2974                    child_target_offset: 0,
2975                    in_chunk_offset: 4096,
2976                    child_length: 4096,
2977                }
2978                .into(),
2979            )
2980            .expect("commit failed");
2981
2982        let mut buf = vec![0u8; 4096];
2983        child_vmo.read(&mut buf, 0).expect("read child_vmo");
2984        assert_eq!(&buf[..100], &[0x42u8; 100]);
2985        assert_eq!(&buf[100..], &[0u8; 3996]);
2986
2987        env.teardown().await;
2988    }
2989
2990    #[fuchsia::test]
2991    async fn test_deliver_child_inline_tail_from_parent_vmo() {
2992        let env = TestEnv::new(32 * 1024).await;
2993        let _parent_vmo =
2994            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
2995
2996        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2997            env.delivery_vmo
2998                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2999                .expect("duplicate_handle failed"),
3000            zx::system_get_page_size() as usize,
3001            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
3002        )
3003        .expect("SyncSender::new failed");
3004
3005        // Register parent blob with 32 KiB verification alignment.
3006        let mut payload =
3007            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
3008        payload.data().copy_from_slice(&env.valid_leaves);
3009        let off = payload.offset();
3010        payload
3011            .commit(
3012                DeliveryCommand::RegisterBlob {
3013                    key: TEST_VMO_KEY,
3014                    offset: off,
3015                    length: env.valid_leaves.len() as u32,
3016                    read_alignment: 32 * 1024,
3017                }
3018                .into(),
3019            )
3020            .expect("commit failed");
3021
3022        let blob =
3023            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
3024        while blob.merkle_verifier.get().is_none() {
3025            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
3026        }
3027
3028        // Register a child VMO with size 100 bytes (inline tail at offset 4096 + 500 in parent).
3029        let child_key = 77u64;
3030        let child_port = zx::Port::create();
3031        let child_vmo = env
3032            .pager_and_verifier
3033            .cache
3034            .pager
3035            .create_vmo(zx::VmoOptions::empty(), &child_port, child_key, 100)
3036            .unwrap();
3037        env.pager_and_verifier.cache.register_child_vmo(
3038            TEST_VMO_KEY,
3039            child_key,
3040            child_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
3041            100,
3042        );
3043
3044        // Push ChildInlineTail BEFORE the parent VMO page is populated!
3045        // This verifies that `ChildInlineTail` pops the FIFO message first and does not deadlock
3046        // when `cached_blob.vmo.read(...)` blocks waiting for a subsequent `DeliveryCommand::Data`.
3047        sender
3048            .reserve_payload(0)
3049            .expect("reserve_payload failed")
3050            .commit(
3051                DeliveryCommand::ChildInlineTail {
3052                    parent_byte_offset: 4096 + 500,
3053                    child_key,
3054                    child_target_offset: 0,
3055                    tail_length: 100,
3056                }
3057                .into(),
3058            )
3059            .expect("commit failed");
3060
3061        // Now deliver the parent blob's 32 KiB data chunk via `DeliveryCommand::Data`.
3062        let mut payload = sender.reserve_payload(32 * 1024).expect("reserve_payload failed");
3063        payload.data().copy_from_slice(&env.blob_data);
3064        let off = payload.offset();
3065        payload
3066            .commit(
3067                DeliveryCommand::Data {
3068                    key: TEST_VMO_KEY,
3069                    target_offset: 0,
3070                    offset: off,
3071                    length: 32 * 1024,
3072                }
3073                .into(),
3074            )
3075            .expect("commit failed");
3076
3077        let mut buf = vec![0u8; 4096];
3078        child_vmo.read(&mut buf, 0).expect("read child_vmo");
3079        assert_eq!(&buf[..100], &[0x42u8; 100]);
3080        assert_eq!(&buf[100..], &[0u8; 3996]);
3081
3082        env.teardown().await;
3083    }
3084
3085    #[fuchsia::test]
3086    async fn test_get_file_mapper_handler() {
3087        use vfs::ToObjectRequest as _;
3088        use vfs::file::StreamIoConnection;
3089
3090        let (mapping_proxy, mut mapping_stream) =
3091            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
3092        let (mapper_proxy, mut mapper_stream) =
3093            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
3094
3095        let _mapping_task = fasync::Task::spawn(async move {
3096            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
3097                mapping_stream.try_next().await.expect("try_next failed")
3098            {
3099                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
3100                    .expect("zx::Vmo::create failed");
3101                responder.send(Ok(mapping_vmo)).expect("send failed");
3102
3103                let mut session_stream = session.into_stream();
3104                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
3105                {
3106                    match request {
3107                        fmapping::MappingSessionRequest::Open { responder, .. } => {
3108                            responder.send(Ok(8192)).expect("send failed");
3109                        }
3110                        fmapping::MappingSessionRequest::Close { responder, .. } => {
3111                            responder.send(Ok(())).expect("send failed");
3112                        }
3113                        _ => {}
3114                    }
3115                }
3116            }
3117        });
3118
3119        let (child_key_tx, child_key_rx) = oneshot::channel();
3120        let _mapper_task = fasync::Task::spawn(async move {
3121            if let Some(fblock::MapperRequest::OpenSession { session, responder, .. }) =
3122                mapper_stream.try_next().await.expect("try_next failed")
3123            {
3124                responder.send(Ok(())).expect("send failed");
3125                let mut session_stream = session.into_stream();
3126                if let Some(fblock::MapperSessionRequest::OpenChildSession {
3127                    parent_key,
3128                    responder,
3129                    ..
3130                }) = session_stream.try_next().await.expect("try_next failed")
3131                {
3132                    child_key_tx.send(parent_key).expect("send failed");
3133                    responder.send(Ok(())).expect("send failed");
3134                }
3135            }
3136        });
3137
3138        let pager_and_verifier = Arc::new(
3139            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
3140                .await
3141                .expect("BlobPagerAndVerifier::new failed"),
3142        );
3143
3144        let hash: [u8; 32] = [0x66; 32];
3145        let vmo = pager_and_verifier.create_vmo(&hash).await.expect("create_vmo failed");
3146
3147        let scope = vfs::execution_scope::ExecutionScope::new();
3148        let (file_mapper, file_mapper_server) =
3149            fidl::endpoints::create_proxy::<fmapping::FileMapperMarker>();
3150        fidl_fuchsia_io::Flags::PROTOCOL_SERVICE
3151            .to_object_request(file_mapper_server.into_channel())
3152            .handle(|object_request| {
3153                vfs::service::serve(
3154                    pager_and_verifier.get_file_mapper_handler(scope.clone()),
3155                    scope.clone(),
3156                    &fidl_fuchsia_io::Flags::PROTOCOL_SERVICE,
3157                    object_request,
3158                )
3159            });
3160
3161        // 1. Unregistered token returns NOT_FOUND.
3162        let unregistered_token = zx::Event::create();
3163        let (_client, server) = fidl::endpoints::create_endpoints::<fblock::MapperSessionMarker>();
3164        assert_eq!(
3165            file_mapper
3166                .open_session(unregistered_token, zx::Vmo::create(4096).unwrap(), server)
3167                .await
3168                .unwrap(),
3169            Err(zx::Status::NOT_FOUND.into_raw())
3170        );
3171
3172        // 2. Non-readable VmoBlob connection returns ACCESS_DENIED.
3173        let vmo_blob =
3174            vmo_blob::VmoBlob::new(vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(), hash);
3175        let (no_read_proxy, no_read_server) =
3176            fidl::endpoints::create_proxy::<fidl_fuchsia_io::FileMarker>();
3177        let no_read_flags = fidl_fuchsia_io::Flags::PROTOCOL_FILE;
3178        let scope_clone = scope.clone();
3179        let vmo_blob_clone = vmo_blob.clone();
3180        scope.spawn(no_read_flags.to_object_request(no_read_server).handle_async(
3181            async move |object_request| {
3182                object_request
3183                    .create_connection::<StreamIoConnection<_>, _>(
3184                        scope_clone,
3185                        vmo_blob_clone,
3186                        no_read_flags,
3187                    )
3188                    .await
3189            },
3190        ));
3191        let no_read_token = zx::Event::from(
3192            no_read_proxy.get_token().await.unwrap().map_err(zx::Status::err_from_raw).unwrap(),
3193        );
3194        let (_client, server) = fidl::endpoints::create_endpoints::<fblock::MapperSessionMarker>();
3195        assert_eq!(
3196            file_mapper
3197                .open_session(no_read_token, zx::Vmo::create(4096).unwrap(), server)
3198                .await
3199                .unwrap(),
3200            Err(zx::Status::ACCESS_DENIED.into_raw())
3201        );
3202
3203        // 3. Non-VmoBlob file connection returns INVALID_ARGS.
3204        let other_file = vfs::file::vmo::read_only(b"not a vmo blob");
3205        let (other_proxy, other_server) =
3206            fidl::endpoints::create_proxy::<fidl_fuchsia_io::FileMarker>();
3207        fidl_fuchsia_io::PERM_READABLE.to_object_request(other_server).handle(|object_request| {
3208            vfs::file::serve(
3209                other_file,
3210                scope.clone(),
3211                &fidl_fuchsia_io::PERM_READABLE,
3212                object_request,
3213            )
3214        });
3215        let other_token = zx::Event::from(
3216            other_proxy.get_token().await.unwrap().map_err(zx::Status::err_from_raw).unwrap(),
3217        );
3218        let (_client, server) = fidl::endpoints::create_endpoints::<fblock::MapperSessionMarker>();
3219        assert_eq!(
3220            file_mapper
3221                .open_session(other_token, zx::Vmo::create(4096).unwrap(), server)
3222                .await
3223                .unwrap(),
3224            Err(zx::Status::INVALID_ARGS.into_raw())
3225        );
3226
3227        // 4. Readable VmoBlob connection succeeds and opens a child mapper session.
3228        let (read_proxy, read_server) =
3229            fidl::endpoints::create_proxy::<fidl_fuchsia_io::FileMarker>();
3230        let read_flags = fidl_fuchsia_io::PERM_READABLE;
3231        let scope_clone = scope.clone();
3232        scope.spawn(read_flags.to_object_request(read_server).handle_async(
3233            async move |object_request| {
3234                object_request
3235                    .create_connection::<StreamIoConnection<_>, _>(
3236                        scope_clone,
3237                        vmo_blob,
3238                        read_flags,
3239                    )
3240                    .await
3241            },
3242        ));
3243        let valid_token = zx::Event::from(
3244            read_proxy.get_token().await.unwrap().map_err(zx::Status::err_from_raw).unwrap(),
3245        );
3246        let (client_proxy, server) = fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
3247        file_mapper
3248            .open_session(valid_token, zx::Vmo::create(4096).unwrap(), server)
3249            .await
3250            .unwrap()
3251            .map_err(zx::Status::err_from_raw)
3252            .expect("open_session should succeed");
3253
3254        let parent_key = child_key_rx.await.expect("failed to receive parent_key");
3255        assert_eq!(parent_key, TEST_VMO_KEY);
3256
3257        let (child_key, _child_vmo) = client_proxy
3258            .create_vmo(4096, fblock::CreateVmoOptions::empty())
3259            .await
3260            .expect("create_vmo fidl failed")
3261            .expect("create_vmo returned error");
3262        assert!(pager_and_verifier.cache.get_child_vmo(child_key).is_some());
3263
3264        drop(vmo);
3265    }
3266
3267    #[fuchsia::test]
3268    async fn test_multiple_child_sessions_same_parent_blob() {
3269        let blob_size = 32 * 1024usize;
3270        let mut blob_data = vec![0u8; blob_size];
3271        blob_data[4096..8192].fill(0xAA);
3272        blob_data[8192..12288].fill(0xBB);
3273        let (root, leaf_hashes) =
3274            fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
3275        let hash: [u8; 32] = root.into();
3276        let mut valid_leaves = Vec::new();
3277        for leaf in &leaf_hashes {
3278            valid_leaves.extend_from_slice(leaf.as_bytes());
3279        }
3280
3281        let (mapping_proxy, mut mapping_stream) =
3282            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
3283        let (mapper_proxy, mut mapper_stream) =
3284            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
3285
3286        let _mapping_task = fasync::Task::spawn(async move {
3287            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
3288                mapping_stream.try_next().await.expect("try_next failed")
3289            {
3290                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
3291                    .expect("zx::Vmo::create failed");
3292                responder.send(Ok(mapping_vmo)).expect("send failed");
3293
3294                let mut session_stream = session.into_stream();
3295                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
3296                {
3297                    match request {
3298                        fmapping::MappingSessionRequest::Open { responder, .. } => {
3299                            responder.send(Ok(blob_size as u64)).expect("send failed");
3300                        }
3301                        fmapping::MappingSessionRequest::Close { responder, .. } => {
3302                            responder.send(Ok(())).expect("send failed");
3303                        }
3304                        _ => {}
3305                    }
3306                }
3307            }
3308        });
3309
3310        let (tx_delivery, rx_delivery) = oneshot::channel();
3311        let (session_keys_tx, mut session_keys_rx) = futures::channel::mpsc::unbounded();
3312        let _mapper_task = fasync::Task::spawn(async move {
3313            if let Some(fblock::MapperRequest::OpenSession {
3314                session,
3315                delivery_queue,
3316                responder,
3317                ..
3318            }) = mapper_stream.try_next().await.expect("try_next failed")
3319            {
3320                tx_delivery.send(delivery_queue).expect("send failed");
3321                responder.send(Ok(())).expect("send failed");
3322                let mut session_stream = session.into_stream();
3323                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
3324                {
3325                    if let fblock::MapperSessionRequest::OpenChildSession {
3326                        parent_key,
3327                        responder,
3328                        ..
3329                    } = request
3330                    {
3331                        session_keys_tx.unbounded_send(parent_key).expect("unbounded_send failed");
3332                        responder.send(Ok(())).expect("send failed");
3333                    }
3334                }
3335            }
3336        });
3337
3338        let pager_and_verifier = Arc::new(
3339            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
3340                .await
3341                .expect("BlobPagerAndVerifier::new failed"),
3342        );
3343        let delivery_vmo =
3344            rx_delivery.await.expect("rx_delivery wait failed").expect("delivery_vmo was None");
3345        let _parent_vmo = pager_and_verifier.create_vmo(&hash).await.expect("create_vmo failed");
3346
3347        let mut sender = SyncSender::<RawDeliveryCommand>::new(
3348            delivery_vmo,
3349            zx::system_get_page_size() as usize,
3350            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
3351        )
3352        .expect("SyncSender::new failed");
3353
3354        let mut payload =
3355            sender.reserve_payload(valid_leaves.len()).expect("reserve_payload failed");
3356        payload.data().copy_from_slice(&valid_leaves);
3357        let off = payload.offset();
3358        payload
3359            .commit(
3360                DeliveryCommand::RegisterBlob {
3361                    key: TEST_VMO_KEY,
3362                    offset: off,
3363                    length: valid_leaves.len() as u32,
3364                    read_alignment: blob_size as u32,
3365                }
3366                .into(),
3367            )
3368            .expect("commit failed");
3369
3370        let blob = pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
3371        while blob.merkle_verifier.get().is_none() {
3372            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
3373        }
3374
3375        // Open two independent child sessions on the same parent blob.
3376        let (session1_proxy, session1_server) =
3377            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
3378        pager_and_verifier
3379            .open_child_session(&hash, zx::Vmo::create(4096).expect("Vmo::create"), session1_server)
3380            .await
3381            .expect("open_child_session 1 failed");
3382        let session1_parent_key =
3383            futures::StreamExt::next(&mut session_keys_rx).await.expect("session1_key missing");
3384
3385        let (session2_proxy, session2_server) =
3386            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
3387        pager_and_verifier
3388            .open_child_session(&hash, zx::Vmo::create(4096).expect("Vmo::create"), session2_server)
3389            .await
3390            .expect("open_child_session 2 failed");
3391        let session2_parent_key =
3392            futures::StreamExt::next(&mut session_keys_rx).await.expect("session2_key missing");
3393
3394        assert_eq!(session1_parent_key, TEST_VMO_KEY);
3395        assert_eq!(session2_parent_key, TEST_VMO_KEY);
3396
3397        // Create VMOs on both child sessions; each receives a globally unique `child_key`.
3398        let (child_key1, vmo1) = session1_proxy
3399            .create_vmo(4096, fblock::CreateVmoOptions::empty())
3400            .await
3401            .expect("session1 create_vmo fidl failed")
3402            .expect("session1 create_vmo failed");
3403        let (child_key2, vmo2) = session2_proxy
3404            .create_vmo(4096, fblock::CreateVmoOptions::empty())
3405            .await
3406            .expect("session2 create_vmo fidl failed")
3407            .expect("session2 create_vmo failed");
3408        assert_ne!(child_key1, child_key2);
3409
3410        // Supply distinct slices of the parent blob to vmo1 and vmo2.
3411        let mut payload1 = sender.reserve_payload(blob_size).expect("reserve_payload failed");
3412        payload1.data().copy_from_slice(&blob_data);
3413        let off1 = payload1.offset();
3414        payload1
3415            .commit(
3416                DeliveryCommand::ChildData {
3417                    parent_offset: 0,
3418                    offset: off1,
3419                    length: blob_size as u32,
3420                    child_key: child_key1,
3421                    child_target_offset: 0,
3422                    in_chunk_offset: 4096,
3423                    child_length: 4096,
3424                }
3425                .into(),
3426            )
3427            .expect("commit 1 failed");
3428
3429        let mut payload2 = sender.reserve_payload(blob_size).expect("reserve_payload failed");
3430        payload2.data().copy_from_slice(&blob_data);
3431        let off2 = payload2.offset();
3432        payload2
3433            .commit(
3434                DeliveryCommand::ChildData {
3435                    parent_offset: 0,
3436                    offset: off2,
3437                    length: blob_size as u32,
3438                    child_key: child_key2,
3439                    child_target_offset: 0,
3440                    in_chunk_offset: 8192,
3441                    child_length: 4096,
3442                }
3443                .into(),
3444            )
3445            .expect("commit 2 failed");
3446
3447        let mut buf1 = vec![0u8; 4096];
3448        vmo1.read(&mut buf1, 0).expect("vmo1 read failed");
3449        assert_eq!(buf1, vec![0xAAu8; 4096]);
3450
3451        let mut buf2 = vec![0u8; 4096];
3452        vmo2.read(&mut buf2, 0).expect("vmo2 read failed");
3453        assert_eq!(buf2, vec![0xBBu8; 4096]);
3454
3455        // Session 2 cannot close session 1's VMO (`CloseVmo` isolation check).
3456        session2_proxy.close_vmo(child_key1).expect("close_vmo failed");
3457        // Close session 2's own VMO and wait for it to be unregistered.
3458        session2_proxy.close_vmo(child_key2).expect("close_vmo failed");
3459        while pager_and_verifier.cache.get_child_vmo(child_key2).is_some() {
3460            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
3461        }
3462        assert!(pager_and_verifier.cache.get_child_vmo(child_key1).is_some());
3463    }
3464}