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;
46
47use anyhow::{Context, Error, anyhow, bail};
48use event_listener as _;
49use fidl_fuchsia_storage_block as fblock;
50use fidl_fuchsia_storage_mapping as fmapping;
51use fuchsia_async as fasync;
52use fuchsia_hash::{HASH_SIZE, Hash};
53use fuchsia_merkle::{MerkleVerifier, ReadSizedMerkleVerifier};
54use fuchsia_sync::Mutex;
55use std::collections::{HashMap, HashSet, hash_map};
56use std::ops::Range;
57use std::sync::{Arc, OnceLock, Weak};
58use storage_ptr_slice::PtrByteSlice;
59use zx;
60
61// A `fuchsia_async::PacketReceiver` that watches for a VMO to reach zero children and triggers
62// cache eviction.
63struct ZeroChildrenReceiver {
64    blob: OnceLock<Weak<CachedBlob>>,
65}
66
67impl fasync::PacketReceiver for ZeroChildrenReceiver {
68    fn receive_packet(&self, packet: zx::Packet) {
69        if let zx::PacketContents::SignalOne(signals) = packet.contents() {
70            if signals.observed().contains(zx::Signals::VMO_ZERO_CHILDREN) {
71                if let Some(cached_blob) = self.blob.get().and_then(|w| w.upgrade()) {
72                    if let Some(cache) = cached_blob.cache.upgrade() {
73                        cache.on_zero_children(&cached_blob);
74                    }
75                }
76            }
77        }
78    }
79}
80
81// Holds the state and resources for an open blob residing in the cache.
82//
83// When a blob is opened, this bundles:
84// - The root pager VMO that clients clone from (so we can detect when all handles close).
85// - The Fxfs session key used by the block driver to deliver data and to close the mapping.
86// - The Merkle tree verifier used to check incoming pages from the driver.
87// - The ZeroChildrenReceiver registration that watches for zero children to trigger eviction.
88struct CachedBlob {
89    // The parent pager-backed VMO. Clients only receive child clones so the kernel can notify us
90    // via `ZX_VMO_ZERO_CHILDREN` when all clients have dropped their handles.
91    vmo: zx::Vmo,
92    // The Merkle root hash of the blob (its unique identifier).
93    root_hash: [u8; 32],
94    // Weak back-reference to Cache to avoid an Arc reference cycle, since the cache already
95    // holds an Arc to this blob.
96    cache: Weak<Cache>,
97    // Session key assigned by Fxfs for this blob mapping, used in driver delivery commands and when
98    // closing the session.
99    vmo_key: u64,
100    // Blob size in bytes.
101    len: u64,
102    // Keeps ZeroChildrenReceiver registered on the async loop while this blob is alive.
103    registration: fasync::ReceiverRegistration<ZeroChildrenReceiver>,
104    // Merkle tree verifier, initialised once the driver delivers the blob's leaf hashes.
105    merkle_verifier: OnceLock<ReadSizedMerkleVerifier>,
106}
107
108impl CachedBlob {
109    fn create_child(&self) -> Result<zx::Vmo, zx::Status> {
110        self.vmo.create_child(zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE, 0, 0)
111    }
112
113    fn wait_for_zero_children(&self) {
114        let _ = self.vmo.wait_async(
115            fasync::EHandle::local().port(),
116            self.registration.key(),
117            zx::Signals::VMO_ZERO_CHILDREN,
118            zx::WaitAsyncOpts::empty(),
119        );
120    }
121}
122
123impl Drop for CachedBlob {
124    fn drop(&mut self) {
125        if let Some(cache) = self.cache.upgrade() {
126            let session = cache.mapping_session.clone();
127            let vmo_key = self.vmo_key;
128            cache.scope.spawn(async move {
129                let _ = session.close(vmo_key).await;
130            });
131        }
132    }
133}
134
135enum BlobState {
136    // The blob is actively opening: extents are being registered with Fxfs and the pager VMO is
137    // being created. Concurrent requests wait on `event`. Early Merkle leaves delivered by the
138    // driver are placed directly into `verifier`.
139    Opening {
140        root_hash: [u8; 32],
141        event: Arc<event_listener::Event>,
142        verifier: Option<ReadSizedMerkleVerifier>,
143    },
144    // The blob's pager-backed VMO is created and cached. Eviction is triggered when all client
145    // child VMOs are closed (`ZX_VMO_ZERO_CHILDREN`).
146    Ready(Arc<CachedBlob>),
147}
148
149pub(crate) struct ActiveRecording {
150    // Blobs opened by external clients during the active recording window.
151    pub(crate) opened_hashes: HashSet<Hash>,
152    // Maps session key to Merkle root hash for all blobs opened or accessed during the recording
153    // window, retained until recording stops even if a blob is evicted before then. When all child
154    // VMO handles are dropped (`ZX_VMO_ZERO_CHILDREN`), `Cache` evicts the blob and closes its
155    // session key; if the same blob is later opened again during the same recording window, `Cache`
156    // allocates a new monotonically increasing session key for it. Retaining every key to `Hash`
157    // mapping ensures page requests recorded by the driver under any of those keys resolve back to
158    // the blob's `Hash` when recording stops.
159    pub(crate) hash_by_key: HashMap<u64, Hash>,
160}
161
162struct CacheState {
163    next_key: u64,
164    // Maps blob Merkle root hash to the client-allocated mapping session key.
165    keys_by_hash: HashMap<[u8; 32], u64>,
166    blob_states_by_key: HashMap<u64, BlobState>,
167    recording: Option<ActiveRecording>,
168}
169
170impl Default for CacheState {
171    fn default() -> Self {
172        Self {
173            next_key: 1,
174            keys_by_hash: HashMap::default(),
175            blob_states_by_key: HashMap::default(),
176            recording: None,
177        }
178    }
179}
180
181impl CacheState {
182    fn generate_key(&mut self) -> u64 {
183        let key = self.next_key;
184        self.next_key += 1;
185        key
186    }
187}
188
189// Holds a pending reservation for a blob actively being opened.
190//
191// If dropped before `commit` is called (e.g. due to an early FIDL failure, pager VMO creation
192// failure, or cancellation), the `Drop` implementation rolls back the reservation: it evicts the
193// key from the cache, queues a close request with Fxfs, and unblocks waiting tasks.
194struct PendingEntry {
195    cache: Arc<Cache>,
196    key: u64,
197}
198
199impl PendingEntry {
200    fn key(&self) -> u64 {
201        self.key
202    }
203
204    // Fulfills the pending entry by constructing the `CachedBlob`, atomically transitioning the
205    // cache entry from `Opening` to `Ready`, and notifying waiting tasks.
206    fn commit(self, vmo: zx::Vmo, size: u64) -> Arc<CachedBlob> {
207        let zero_children_receiver = ZeroChildrenReceiver { blob: OnceLock::new() };
208        let zero_children_registration =
209            fasync::EHandle::local().register_receiver(zero_children_receiver);
210
211        let (cached, event) = {
212            let mut state = self.cache.state.lock();
213            let (root_hash, event, verifier) = match state.blob_states_by_key.remove(&self.key) {
214                Some(BlobState::Opening { root_hash, event, verifier }) => {
215                    (root_hash, event, verifier)
216                }
217                _ => {
218                    unreachable!("Blob for key {} was unexpectedly not in Opening state", self.key)
219                }
220            };
221
222            let merkle_verifier = OnceLock::new();
223            if let Some(verifier) = verifier {
224                let _ = merkle_verifier.set(verifier);
225            }
226
227            let cached = Arc::new(CachedBlob {
228                vmo,
229                root_hash,
230                cache: Arc::downgrade(&self.cache),
231                vmo_key: self.key,
232                len: size,
233                registration: zero_children_registration,
234                merkle_verifier,
235            });
236
237            cached
238                .registration
239                .receiver()
240                .blob
241                .set(Arc::downgrade(&cached))
242                .expect("Failed to bind CachedBlob to ZeroChildrenReceiver: already initialized");
243
244            state.blob_states_by_key.insert(self.key, BlobState::Ready(cached.clone()));
245            if let Some(recording) = state.recording.as_mut() {
246                recording.hash_by_key.insert(self.key, Hash::from(root_hash));
247            }
248
249            (cached, event)
250        };
251
252        cached.wait_for_zero_children();
253        event.notify(usize::MAX);
254        cached
255    }
256}
257
258impl Drop for PendingEntry {
259    fn drop(&mut self) {
260        let event = {
261            let mut state = self.cache.state.lock();
262            match state.blob_states_by_key.get(&self.key) {
263                // If the blob committed successfully, there is nothing to roll back.
264                Some(BlobState::Ready(_)) => return,
265                Some(BlobState::Opening { .. }) => {
266                    if let Some(BlobState::Opening { root_hash, event, .. }) =
267                        state.blob_states_by_key.remove(&self.key)
268                    {
269                        state.keys_by_hash.remove(&root_hash);
270                        event
271                    } else {
272                        return;
273                    }
274                }
275                None => {
276                    log::error!(
277                        key = self.key;
278                        "PendingEntry::drop: key unexpectedly absent from cache"
279                    );
280                    return;
281                }
282            }
283        };
284
285        // Queue the close on Fxfs before waking waiters.
286        // Note: If `open()` was never called or returned an error, closing this key is safe
287        // because the block driver ignores `CloseBlob` for unknown keys. However, if `open()` was
288        // in-flight when cancelled, or if `open()` succeeded but subsequent operations failed, this
289        // close prevents leaking extent mappings.
290        let session = self.cache.mapping_session.clone();
291        let key = self.key;
292        self.cache.scope.spawn(async move {
293            let _ = session.close(key).await;
294        });
295
296        // Wake up all suspended tasks concurrently waiting on this blob.
297        event.notify(usize::MAX);
298    }
299}
300
301// The outcome of looking up or reserving an entry in `Cache`.
302enum CacheLookup {
303    // The VMO has been created and cached. Returns a child of it.
304    Ready(zx::Vmo),
305    // The VMO is currently being created by another request. Contains a listener to await
306    // completion.
307    Pending(event_listener::EventListener),
308    // The blob is absent. Provides a drop-safe pending entry to be used with creation. If the task
309    // crashes or returns an early error, the pending state will be cleared from the cache.
310    Missing(PendingEntry),
311}
312
313// Coordinates the concurrent creation, caching, and verification of pager-backed blobs.
314pub(crate) struct Cache {
315    state: Mutex<CacheState>,
316    pub(crate) mapping_session: fmapping::MappingSessionProxy,
317    pager: Arc<zx::Pager>,
318    port: zx::Port,
319    delivery_vmo: zx::Vmo,
320    pub(crate) scope: fasync::ScopeHandle,
321}
322
323impl Cache {
324    fn new(
325        mapping_session: fmapping::MappingSessionProxy,
326        pager: Arc<zx::Pager>,
327        port: zx::Port,
328        delivery_vmo: zx::Vmo,
329        scope: fasync::ScopeHandle,
330    ) -> Self {
331        Self {
332            state: Mutex::new(CacheState::default()),
333            mapping_session,
334            pager,
335            port,
336            delivery_vmo,
337            scope,
338        }
339    }
340
341    fn delivery_vmo(&self) -> &zx::Vmo {
342        &self.delivery_vmo
343    }
344
345    fn get_by_key(&self, key: u64) -> Option<Arc<CachedBlob>> {
346        let state = self.state.lock();
347        match state.blob_states_by_key.get(&key) {
348            Some(BlobState::Ready(cached)) => Some(cached.clone()),
349            _ => None,
350        }
351    }
352
353    #[cfg(test)]
354    fn is_merkle_initialized(&self, key: u64) -> bool {
355        let state = self.state.lock();
356        match state.blob_states_by_key.get(&key) {
357            Some(BlobState::Opening { verifier, .. }) => verifier.is_some(),
358            Some(BlobState::Ready(blob)) => blob.merkle_verifier.get().is_some(),
359            None => false,
360        }
361    }
362
363    pub(crate) fn start_recording(&self) -> Result<(), zx::Status> {
364        let mut state = self.state.lock();
365        if state.recording.is_some() {
366            return Err(zx::Status::BAD_STATE);
367        }
368        let hash_by_key =
369            state.keys_by_hash.iter().map(|(&hash, &key)| (key, Hash::from(hash))).collect();
370        state.recording = Some(ActiveRecording { opened_hashes: HashSet::new(), hash_by_key });
371        Ok(())
372    }
373
374    fn record_open(&self, identifier: &[u8; 32]) {
375        let mut state = self.state.lock();
376        if let Some(recording) = state.recording.as_mut() {
377            recording.opened_hashes.insert(Hash::from(*identifier));
378        }
379    }
380
381    pub(crate) fn take_recording(&self) -> Option<ActiveRecording> {
382        self.state.lock().recording.take()
383    }
384
385    pub(crate) fn abort_recording(&self) {
386        self.state.lock().recording = None;
387    }
388
389    /// Looks up the blob in the cache and returns its current [`CacheLookup`] state:
390    ///
391    /// - [`CacheLookup::Ready`]: The blob is already cached. Returns a new read-only child VMO.
392    /// - [`CacheLookup::Pending`]: Another task is actively opening this blob. Returns an
393    ///   `EventListener` so the caller can await completion without duplicate work.
394    /// - [`CacheLookup::Missing`]: The blob is absent. Atomically reserves the key and slot
395    ///   by inserting `BlobState::Opening`, returning a [`PendingEntry`] for the caller to fulfill.
396    fn lookup(self: &Arc<Self>, identifier: &[u8; 32]) -> Result<CacheLookup, Error> {
397        let mut state = self.state.lock();
398        if let Some(&key) = state.keys_by_hash.get(identifier) {
399            match state.blob_states_by_key.get(&key) {
400                Some(BlobState::Ready(cached)) => {
401                    let child = cached
402                        .create_child()
403                        .map_err(|s| anyhow!("Failed to create child VMO: {s}"))?;
404                    return Ok(CacheLookup::Ready(child));
405                }
406                Some(BlobState::Opening { event, .. }) => {
407                    return Ok(CacheLookup::Pending(event.listen()));
408                }
409                None => unreachable!("keys_by_hash pointed to non-existent key {key}"),
410            }
411        }
412
413        let event = Arc::new(event_listener::Event::new());
414        let key = state.generate_key();
415        state.keys_by_hash.insert(*identifier, key);
416        state
417            .blob_states_by_key
418            .insert(key, BlobState::Opening { root_hash: *identifier, event, verifier: None });
419        Ok(CacheLookup::Missing(PendingEntry { cache: self.clone(), key }))
420    }
421
422    pub(crate) async fn open_vmo(
423        self: &Arc<Self>,
424        identifier: &[u8; 32],
425    ) -> Result<zx::Vmo, Error> {
426        loop {
427            let entry = match self.lookup(identifier)? {
428                CacheLookup::Ready(child) => return Ok(child),
429                CacheLookup::Pending(listener) => {
430                    listener.await;
431                    continue; // Re-evaluate cache now that the blocking event triggered
432                }
433                CacheLookup::Missing(entry) => entry,
434            };
435
436            let key = entry.key();
437
438            // Ask Fxfs to register the blob and write its extent mappings into the shared mapping
439            // VMO, using the client-allocated key.
440            let size = self
441                .mapping_session
442                .open(key, identifier)
443                .await
444                .context("FIDL error calling MappingSession.Open")?
445                .map_err(|e| {
446                    Error::from(zx::Status::err_from_raw(e))
447                        .context("MappingSession.Open failed for blob")
448                })?;
449
450            let vmo = self.pager.create_vmo(zx::VmoOptions::empty(), &self.port, key, size)?;
451            let vmo_name = format!("blob-{}", &Hash::from(*identifier).to_string()[..8]);
452            if let Ok(name) = zx::Name::new(&vmo_name) {
453                let _ = vmo.set_name(&name);
454            }
455
456            // Create the initial child. We vend children of this VMO to clients so we can track
457            // when all children are dropped to evict the blob from cache.
458            let first_child = vmo
459                .create_child(zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE, 0, 0)
460                .map_err(|s| anyhow!("Failed to create child VMO: {}", s))?;
461
462            entry.commit(vmo, size);
463
464            return Ok(first_child);
465        }
466    }
467
468    fn on_zero_children(&self, blob: &Arc<CachedBlob>) {
469        // On zero children, evict the blob from the cache and close its Fxfs mapping session.
470        // We must hold this lock before checking `num_children` so `lookup` cannot clone
471        // a child between this check and the following eviction, which would leave that client
472        // holding a VMO whose storage mappings have already been closed.
473        let mut state = self.state.lock();
474        if let Ok(info) = blob.vmo.info() {
475            if info.num_children > 0 {
476                // A concurrent client acquired a child VMO before we processed this packet.
477                // Resume watching for the next ZERO_CHILDREN signal.
478                blob.wait_for_zero_children();
479                return;
480            }
481        }
482        // Evict from `keys_by_hash`.
483        if let hash_map::Entry::Occupied(entry) = state.keys_by_hash.entry(blob.root_hash) {
484            if *entry.get() == blob.vmo_key {
485                entry.remove();
486            }
487        }
488        // Evict from `blob_states_by_key`.
489        if let hash_map::Entry::Occupied(entry) = state.blob_states_by_key.entry(blob.vmo_key) {
490            if let BlobState::Ready(cached) = entry.get() {
491                if Arc::ptr_eq(cached, blob) {
492                    entry.remove();
493                }
494            }
495        }
496    }
497
498    fn create_merkle_verifier(
499        root_hash: &[u8; 32],
500        vmo_key: u64,
501        hashes: Box<[Hash]>,
502        read_alignment: usize,
503    ) -> Result<ReadSizedMerkleVerifier, Error> {
504        // For blobs <= 8192 bytes (a single block), the Merkle tree consists of only a single leaf,
505        // which is identical to the root hash itself. Fxfs omits storing leaf hashes on disk for
506        // single-block blobs to save space, resulting in empty leaf metadata read by the block
507        // server. In that case, synthesise the single leaf from the blob's Merkle root hash.
508        let hashes = if hashes.is_empty() { Box::new([Hash::from(*root_hash)]) } else { hashes };
509
510        match MerkleVerifier::new(Hash::from(*root_hash), hashes) {
511            Ok(verifier) => ReadSizedMerkleVerifier::new(verifier, read_alignment)
512                .map_err(|e| anyhow!("Failed to create ReadSizedMerkleVerifier: {e:?}")),
513            Err(e) => {
514                bail!("Failed to verify merkle leaves for key {vmo_key}: {e:?}");
515            }
516        }
517    }
518
519    fn report_pager_failure(&self, vmo: &zx::Vmo, range: Range<u64>, status: zx::Status) {
520        // Map to the set of statuses accepted by ZX_PAGER_OP_FAIL which are ZX_ERR_IO,
521        // ZX_ERR_IO_DATA_INTEGRITY, ZX_ERR_BAD_STATE, ZX_ERR_NO_SPACE, and ZX_ERR_BUFFER_TOO_SMALL.
522        let pager_status = match status {
523            zx::Status::IO_DATA_INTEGRITY => zx::Status::IO_DATA_INTEGRITY,
524            zx::Status::NO_SPACE => zx::Status::NO_SPACE,
525            zx::Status::BUFFER_TOO_SMALL | zx::Status::FILE_BIG => zx::Status::BUFFER_TOO_SMALL,
526            zx::Status::IO
527            | zx::Status::IO_DATA_LOSS
528            | zx::Status::IO_INVALID
529            | zx::Status::IO_MISSED_DEADLINE
530            | zx::Status::IO_NOT_PRESENT
531            | zx::Status::IO_OVERRUN
532            | zx::Status::IO_REFUSED
533            | zx::Status::PEER_CLOSED => zx::Status::IO,
534            _ => zx::Status::BAD_STATE,
535        };
536
537        // `ZX_PAGER_OP_FAIL` resolves pending page requests overlapping the specified range with
538        // the failure status. Pages that were already supplied remain accessible.
539        if let Err(error) = self.pager.op_range(zx::PagerOp::Fail(pager_status), vmo, range) {
540            log::error!(error:?; "Failed to report pager failure to kernel");
541        }
542    }
543}
544
545impl delivery::DeliveryQueueProvider for Cache {
546    fn deliver_pages(
547        &self,
548        key: u64,
549        target_offset: u64,
550        unverified_pages: UnverifiedPages<'_>,
551    ) -> Result<(), Error> {
552        let cached_blob = self
553            .get_by_key(key)
554            .ok_or_else(|| anyhow!("Unknown or expired key {key} in deliver_pages"))?;
555
556        let page_size = zx::system_get_page_size() as u64;
557        let page_aligned_size = cached_blob.len.div_ceil(page_size) * page_size;
558
559        let verifier = match cached_blob.merkle_verifier.get() {
560            Some(v) => v,
561            None => {
562                // If data arrives before `RegisterBlob` (or if registration failed), the blob has
563                // no cryptographic metadata, so no pages can ever be verified or supplied. Fail the
564                // entire page-aligned range of the VMO with `BAD_STATE` to unblock any waiting
565                // client threads on this blob.
566                self.report_pager_failure(
567                    &cached_blob.vmo,
568                    0..page_aligned_size,
569                    zx::Status::BAD_STATE,
570                );
571                bail!("Data received for uninitialized blob {}", key);
572            }
573        };
574
575        let chunk_len = unverified_pages.len_in_bytes() as u64;
576
577        // `zx_pager_supply_pages` and `zx_pager_op_range(Fail)` require the range to be within the
578        // page-aligned capacity of the VMO. If the range extends past the end of the VMO, the
579        // kernel fails the call with `ZX_ERR_OUT_OF_RANGE`. Clamp the range to the VMO's
580        // page-aligned limit.
581        let bounded_len = std::cmp::min(chunk_len, page_aligned_size.saturating_sub(target_offset));
582        let chunk_range = target_offset..target_offset + bounded_len;
583
584        // For the final chunk of an unaligned blob, the buffer passed to `verify_aligned` is
585        // page-aligned and zero-padded, but `verify_aligned` expects the remaining unaligned length
586        // of the valid data.
587        let remaining_in_blob = cached_blob.len.saturating_sub(target_offset);
588        let unaligned_len = std::cmp::min(chunk_len, remaining_in_blob) as usize;
589        if let Err(e) = verifier.verify_aligned(
590            target_offset as usize,
591            unverified_pages.as_ptr_byte_slice(),
592            unaligned_len,
593        ) {
594            self.report_pager_failure(&cached_blob.vmo, chunk_range, zx::Status::IO_DATA_INTEGRITY);
595            bail!("Failed to verify payload for blob {}: {:?}", key, e);
596        }
597
598        if let Err(e) = self.pager.supply_pages(
599            &cached_blob.vmo,
600            chunk_range.clone(),
601            &self.delivery_vmo,
602            unverified_pages.delivery_offset(),
603        ) {
604            self.report_pager_failure(&cached_blob.vmo, chunk_range, e);
605            bail!("Failed to supply pages: {:?}", e);
606        }
607
608        Ok(())
609    }
610
611    fn register_blob(
612        &self,
613        key: u64,
614        leaf_data: PtrByteSlice<'_>,
615        read_alignment: usize,
616    ) -> Result<(), Error> {
617        if leaf_data.len() % HASH_SIZE != 0 {
618            bail!("RegisterBlob invalid leaf length must be a multiple of HASH_SIZE");
619        }
620
621        let hashes: Vec<Hash> = (0..(leaf_data.len() / HASH_SIZE))
622            .map(|i| {
623                let chunk = leaf_data.subslice(i * HASH_SIZE..(i + 1) * HASH_SIZE);
624                let mut hash_bytes = [0u8; HASH_SIZE];
625                chunk.copy_to_slice(&mut hash_bytes);
626                Hash::from(hash_bytes)
627            })
628            .collect();
629
630        let mut state = self.state.lock();
631        match state.blob_states_by_key.get_mut(&key) {
632            Some(BlobState::Opening { root_hash, verifier, .. }) => {
633                if verifier.is_some() {
634                    log::warn!(key;
635                        "RegisterBlob received for blob but it is already initialized"
636                    );
637                    return Ok(());
638                }
639                *verifier = Some(Self::create_merkle_verifier(
640                    root_hash,
641                    key,
642                    hashes.into_boxed_slice(),
643                    read_alignment,
644                )?);
645                Ok(())
646            }
647            Some(BlobState::Ready(cached)) => {
648                if cached.merkle_verifier.get().is_some() {
649                    log::warn!(key;
650                        "RegisterBlob received for blob but it is already initialized"
651                    );
652                    return Ok(());
653                }
654                let verifier = Self::create_merkle_verifier(
655                    &cached.root_hash,
656                    key,
657                    hashes.into_boxed_slice(),
658                    read_alignment,
659                )?;
660                let _ = cached.merkle_verifier.set(verifier);
661                Ok(())
662            }
663            None => {
664                bail!("RegisterBlob received for unknown or expired key {key}");
665            }
666        }
667    }
668}
669
670/// Coordinates between FxBlob, the block driver, and the kernel to manage pager-backed blobs such
671/// that page requests can be handled at the driver instead of the filesystem layer.
672///
673/// `BlobPagerAndVerifier` uses a dedicated Pager to create pager-backed VMOs for blobs. Kernel page
674/// faults on these VMOs are routed directly to the block driver, which reads and decompresses the
675/// raw data. The driver then passes this data back to `BlobPagerAndVerifier` for cryptographic
676/// verification and page fault resolution.
677pub struct BlobPagerAndVerifier {
678    // Ties the lifetime of the background delivery thread to the `BlobPagerAndVerifier`.
679    _delivery_processor: delivery::DeliveryQueueProcessor,
680    mapper_session: fblock::MapperSessionProxy,
681    cache: Arc<Cache>,
682    scope: fasync::Scope,
683}
684
685impl BlobPagerAndVerifier {
686    /// Returns a reference to the shared delivery queue VMO.
687    pub fn delivery_vmo(&self) -> &zx::Vmo {
688        self.cache.delivery_vmo()
689    }
690
691    /// Creates a new BlobPagerAndVerifier.
692    ///
693    /// Establishes a mapping_provider session with FxBlob to receive a shared VMO that will be used
694    /// to communicate opened blob extent mappings as well as any closed blobs. Then, opens a
695    /// mapper session with the block driver, forwarding the mapping VMO alongside a Zircon port
696    /// (for the driver to receive page faults) and a delivery queue VMO (for the driver to emit
697    /// unverified data for verification).
698    pub async fn new(
699        mapping_provider: &fmapping::MappingProviderProxy,
700        mapper: &fblock::MapperProxy,
701    ) -> Result<Self, Error> {
702        // Establish a mapping_provider session with Fxfs.
703        let (mapping_session, session_server) =
704            fidl::endpoints::create_proxy::<fmapping::MappingSessionMarker>();
705        let mapping_vmo = mapping_provider
706            .open_session(session_server)
707            .await
708            .context("FIDL error calling MappingProvider.OpenSession")?
709            .map_err(|e| anyhow!("MappingProvider open_session failed: {e:?}"))?;
710
711        // Establish a mapper session with the block driver.
712        let port = zx::Port::create();
713        let delivery_queue = zx::Vmo::create(mapping::DELIVERY_VMO_SIZE)
714            .context("Failed to create delivery queue VMO")?;
715        let delivery_vmo: zx::Vmo = delivery_queue
716            .duplicate_handle(zx::Rights::SAME_RIGHTS)
717            .context("Failed to duplicate delivery queue VMO")?;
718        let delivery_queue_dup = delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
719        let receiver = vmo_fifo::Receiver::<mapping::RawDeliveryCommand>::new(
720            delivery_queue,
721            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
722        )
723        .context("Failed to create delivery queue receiver")?;
724        let pager = Arc::new(
725            zx::Pager::create(zx::PagerOptions::empty()).context("Failed to create pager")?,
726        );
727        let port_dup = port.duplicate_handle(zx::Rights::SAME_RIGHTS)?;
728        let (mapper_session, mapper_session_server) =
729            fidl::endpoints::create_proxy::<fblock::MapperSessionMarker>();
730        mapper
731            .open_session(
732                mapper_session_server,
733                mapping_vmo,
734                Some(port_dup),
735                Some(delivery_queue_dup),
736            )
737            .await
738            .context("FIDL error calling Mapper.OpenSession")?
739            .map_err(|e| anyhow!("Mapper.OpenSession failed: {e:?}"))?;
740
741        let scope = fasync::Scope::new();
742        let scope_handle = scope.to_handle();
743        let cache = Arc::new(Cache::new(
744            mapping_session,
745            pager,
746            port,
747            delivery_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?,
748            scope_handle,
749        ));
750        let _delivery_processor =
751            delivery::DeliveryQueueProcessor::spawn(receiver, cache.clone(), delivery_vmo)?;
752
753        Ok(Self { _delivery_processor, mapper_session, cache, scope })
754    }
755
756    /// Starts recording a profile for `name` and replaying any existing profile of the same name.
757    pub async fn start_profile(
758        &self,
759        name: impl Into<String>,
760    ) -> Result<ProfileRecording, zx::Status> {
761        ProfileRecording::start(self.cache.clone(), &self.mapper_session, name.into()).await
762    }
763
764    /// Starts recording and replaying the named profile, stopping and committing the profile in the
765    /// background after `duration` elapses.
766    pub async fn record_and_replay_profile(
767        &self,
768        name: impl Into<String>,
769        duration: zx::MonotonicDuration,
770    ) {
771        let name = name.into();
772        let recording = match self.start_profile(name.clone()).await {
773            Ok(recording) => recording,
774            Err(status) => {
775                log::warn!(status:?, name = name.as_str(); "Failed to start profile recording");
776                return;
777            }
778        };
779        self.scope.spawn(async move {
780            fasync::Timer::new(duration).await;
781            if let Err(status) = recording.stop().await {
782                log::warn!(status:?, name = name.as_str(); "Failed to stop profile recording");
783            }
784        });
785    }
786
787    /// Create pager owned VMO for the blob identified by its Merkle Root Hash.
788    pub async fn create_vmo(&self, identifier: &[u8; 32]) -> Result<zx::Vmo, Error> {
789        let vmo = self.cache.open_vmo(identifier).await?;
790        self.cache.record_open(identifier);
791        Ok(vmo)
792    }
793}
794
795#[cfg(test)]
796mod tests {
797    use super::*;
798    use futures::TryStreamExt;
799    use futures::channel::oneshot;
800    use mapping::{DeliveryCommand, RawDeliveryCommand};
801    use test_case::test_case;
802    use vmo_fifo::SyncSender;
803
804    const TARGET_READ_AHEAD_SIZE: usize = mapping::TARGET_READ_AHEAD_SIZE as usize;
805    const TEST_BLOB_SIZE: u64 = (TARGET_READ_AHEAD_SIZE * 2) as u64;
806    const TEST_VMO_KEY: u64 = 1;
807
808    // Used for testing BlobPagerAndVerifier interactions.
809    // We arbitrarily allocate a 32KiB buffer so the payload spans multiple VMO pages.
810    //
811    // This test environment assumes a single blob file, identified by the `valid_root` hash.
812    struct TestEnv {
813        pager_and_verifier: Arc<BlobPagerAndVerifier>,
814        mapping_task: fasync::Task<()>,
815        mapper_task: fasync::Task<()>,
816        close_signal: Option<oneshot::Receiver<()>>,
817        delivery_vmo: zx::Vmo,
818        pub valid_root: [u8; 32],
819        pub valid_leaves: Vec<u8>,
820        pub blob_data: Vec<u8>,
821    }
822
823    impl TestEnv {
824        async fn new(blob_size: u64) -> Self {
825            let blob_data = vec![0x42u8; blob_size as usize];
826            let (root, leaf_hashes) =
827                fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
828            let expected_hash: [u8; 32] = root.into();
829
830            let mut flat_leaves = Vec::new();
831            for hash in &leaf_hashes {
832                flat_leaves.extend_from_slice(hash.as_bytes());
833            }
834
835            let (mapping_proxy, mut mapping_stream) =
836                fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
837
838            let (close_tx, close_rx) = oneshot::channel();
839            let mapping_task = fasync::Task::spawn(async move {
840                let mut close_tx = Some(close_tx);
841                if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
842                    mapping_stream.try_next().await.expect("try_next failed")
843                {
844                    let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
845                        .expect("zx::Vmo::create failed");
846                    responder.send(Ok(mapping_vmo)).expect("send failed");
847
848                    let mut session_stream = session.into_stream();
849                    let mut open_calls = 0;
850                    while let Some(request) =
851                        session_stream.try_next().await.expect("try_next failed")
852                    {
853                        match request {
854                            fmapping::MappingSessionRequest::Open {
855                                key,
856                                identifier,
857                                responder,
858                            } => {
859                                assert_eq!(identifier, expected_hash);
860                                assert_eq!(key, TEST_VMO_KEY);
861                                open_calls += 1;
862                                assert_eq!(
863                                    open_calls, 1,
864                                    "Open should only be called once per identifier"
865                                );
866                                responder.send(Ok(blob_size)).expect("send failed");
867                            }
868                            fmapping::MappingSessionRequest::Close { key, responder } => {
869                                assert_eq!(key, TEST_VMO_KEY);
870                                responder.send(Ok(())).expect("send failed");
871                                if let Some(tx) = close_tx.take() {
872                                    let _ = tx.send(());
873                                }
874                            }
875                            _ => {}
876                        }
877                    }
878                }
879            });
880
881            let (mapper_proxy, mut mapper_stream) =
882                fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
883
884            let (tx_delivery, rx_delivery) = oneshot::channel();
885            let mapper_task = fasync::Task::spawn(async move {
886                if let Some(fblock::MapperRequest::OpenSession {
887                    delivery_queue, responder, ..
888                }) = mapper_stream.try_next().await.expect("try_next failed")
889                {
890                    tx_delivery.send(delivery_queue).expect("send failed");
891                    responder.send(Ok(())).expect("send failed");
892                }
893            });
894
895            Self {
896                pager_and_verifier: Arc::new(
897                    BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
898                        .await
899                        .expect("BlobPagerAndVerifier::new failed"),
900                ),
901                mapping_task,
902                mapper_task,
903                close_signal: Some(close_rx),
904                delivery_vmo: rx_delivery
905                    .await
906                    .expect("rx_delivery wait failed")
907                    .expect("delivery_vmo was None"),
908                valid_root: expected_hash,
909                valid_leaves: flat_leaves,
910                blob_data,
911            }
912        }
913
914        async fn teardown(self) {
915            drop(self.pager_and_verifier);
916            self.mapping_task.await;
917            self.mapper_task.await;
918        }
919    }
920
921    struct ExpectedReadThread {
922        started_rx: Option<oneshot::Receiver<()>>,
923        done_rx: oneshot::Receiver<()>,
924        handle: std::thread::JoinHandle<()>,
925    }
926
927    impl ExpectedReadThread {
928        async fn wait_started(&mut self) {
929            if let Some(rx) = self.started_rx.take() {
930                rx.await.expect("reader thread failed to start");
931            }
932        }
933
934        async fn wait_and_verify(self) {
935            self.done_rx.await.expect("reader thread panicked or hung");
936            self.handle.join().expect("join failed");
937        }
938    }
939
940    fn spawn_reader_expect_error(
941        vmo: &zx::Vmo,
942        offset: u64,
943        size: usize,
944        expected_status: zx::Status,
945    ) -> ExpectedReadThread {
946        let vmo_clone =
947            vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
948        let (started_tx, started_rx) = oneshot::channel();
949        let (done_tx, done_rx) = oneshot::channel();
950
951        let handle = std::thread::spawn(move || {
952            let mut buf = vec![0u8; size];
953            let _ = started_tx.send(());
954            let err = vmo_clone.read(&mut buf, offset).expect_err("read should fail");
955            assert_eq!(err, expected_status);
956            let _ = done_tx.send(());
957        });
958
959        ExpectedReadThread { started_rx: Some(started_rx), done_rx, handle }
960    }
961
962    #[fuchsia::test]
963    async fn test_create_vmo() {
964        let env = TestEnv::new(TEST_BLOB_SIZE).await;
965        let vmo =
966            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO");
967        let size = vmo.get_size().expect("get_size failed");
968
969        // Pager VMO sizes are rounded up to the nearest page boundary.
970        let page_size = zx::system_get_page_size() as u64;
971        let expected_pages = (TEST_BLOB_SIZE + page_size - 1) / page_size;
972        assert_eq!(size, expected_pages * page_size);
973
974        // Explicitly drop the VMO so `ZX_VMO_ZERO_CHILDREN` fires and the mock mapping
975        // server receives the `.close()` IPC. Otherwise `teardown` will deadlock waiting
976        // for the server loop to exit!
977        drop(vmo);
978
979        env.teardown().await;
980    }
981
982    #[fuchsia::test]
983    async fn test_create_vmo_concurrent_access() {
984        let env = TestEnv::new(TEST_BLOB_SIZE).await;
985        let mut futures = vec![];
986        for _ in 0..10 {
987            let verifier = env.pager_and_verifier.clone();
988            futures.push(fasync::Task::spawn(async move {
989                verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO")
990            }));
991        }
992        let vmos = futures::future::join_all(futures).await;
993
994        drop(vmos);
995        env.teardown().await;
996    }
997
998    #[fuchsia::test]
999    async fn test_zero_children_eviction() {
1000        let mut env = TestEnv::new(TEST_BLOB_SIZE).await;
1001
1002        let child_vmo =
1003            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1004
1005        {
1006            let state = env.pager_and_verifier.cache.state.lock();
1007            assert_eq!(state.keys_by_hash.get(&env.valid_root), Some(&TEST_VMO_KEY));
1008            assert!(matches!(
1009                state.blob_states_by_key.get(&TEST_VMO_KEY),
1010                Some(BlobState::Ready(_))
1011            ));
1012        }
1013
1014        drop(child_vmo);
1015
1016        // wait for the packet receiver to observe the ZERO_CHILDREN signal and dispatch
1017        // `mapping_session.close()` to the mapping_server.
1018        env.close_signal.take().expect("Missing signal").await.expect("Failed to close");
1019
1020        {
1021            let state = env.pager_and_verifier.cache.state.lock();
1022            assert!(state.keys_by_hash.get(&env.valid_root).is_none());
1023            assert!(state.blob_states_by_key.get(&TEST_VMO_KEY).is_none());
1024        }
1025
1026        env.teardown().await;
1027    }
1028
1029    #[fuchsia::test]
1030    async fn test_create_vmo_multiple_distinct_blobs() {
1031        use futures::StreamExt;
1032
1033        let (mapping_proxy, mut mapping_stream) =
1034            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1035        let (mapper_proxy, mut mapper_stream) =
1036            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1037
1038        let (close_tx, mut close_rx) = futures::channel::mpsc::unbounded();
1039        let mapping_task = fasync::Task::spawn(async move {
1040            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1041                mapping_stream.try_next().await.expect("try_next failed")
1042            {
1043                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1044                    .expect("zx::Vmo::create failed");
1045                responder.send(Ok(mapping_vmo)).expect("send failed");
1046
1047                let mut session_stream = session.into_stream();
1048                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
1049                {
1050                    match request {
1051                        fmapping::MappingSessionRequest::Open { responder, .. } => {
1052                            responder.send(Ok(8192)).expect("send failed");
1053                        }
1054                        fmapping::MappingSessionRequest::Close { key, responder } => {
1055                            responder.send(Ok(())).expect("send failed");
1056                            let _ = close_tx.unbounded_send(key);
1057                        }
1058                        _ => {}
1059                    }
1060                }
1061            }
1062        });
1063
1064        let mapper_task = fasync::Task::spawn(async move {
1065            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1066                mapper_stream.try_next().await.expect("try_next failed")
1067            {
1068                responder.send(Ok(())).expect("send failed");
1069            }
1070        });
1071
1072        let pager_and_verifier = Arc::new(
1073            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1074                .await
1075                .expect("BlobPagerAndVerifier::new failed"),
1076        );
1077
1078        let hash1: [u8; 32] = [0x11; 32];
1079        let hash2: [u8; 32] = [0x22; 32];
1080        let hash3: [u8; 32] = [0x33; 32];
1081
1082        let vmo1 = pager_and_verifier.create_vmo(&hash1).await.expect("create_vmo 1 failed");
1083        assert_eq!(
1084            vmo1.get_name().expect("get_name failed"),
1085            zx::Name::new("blob-11111111").expect("zx::Name::new failed")
1086        );
1087        let vmo2 = pager_and_verifier.create_vmo(&hash2).await.expect("create_vmo 2 failed");
1088        let vmo3 = pager_and_verifier.create_vmo(&hash3).await.expect("create_vmo 3 failed");
1089
1090        let (key1, key2, key3) = {
1091            let state = pager_and_verifier.cache.state.lock();
1092            let k1 = *state.keys_by_hash.get(&hash1).expect("hash1 missing");
1093            let k2 = *state.keys_by_hash.get(&hash2).expect("hash2 missing");
1094            let k3 = *state.keys_by_hash.get(&hash3).expect("hash3 missing");
1095            assert_ne!(k1, k2);
1096            assert_ne!(k2, k3);
1097            assert_ne!(k1, k3);
1098            assert!(matches!(state.blob_states_by_key.get(&k1), Some(BlobState::Ready(_))));
1099            assert!(matches!(state.blob_states_by_key.get(&k2), Some(BlobState::Ready(_))));
1100            assert!(matches!(state.blob_states_by_key.get(&k3), Some(BlobState::Ready(_))));
1101            (k1, k2, k3)
1102        };
1103
1104        // Evict blob 2 by dropping its only child VMO
1105        drop(vmo2);
1106        let closed_key = close_rx.next().await.expect("expected close signal");
1107        assert_eq!(closed_key, key2);
1108
1109        // Verify blob 2 was evicted while blob 1 and 3 remain cached
1110        {
1111            let state = pager_and_verifier.cache.state.lock();
1112            assert!(state.keys_by_hash.get(&hash2).is_none());
1113            assert!(state.blob_states_by_key.get(&key2).is_none());
1114            assert_eq!(state.keys_by_hash.get(&hash1), Some(&key1));
1115            assert_eq!(state.keys_by_hash.get(&hash3), Some(&key3));
1116            assert!(matches!(state.blob_states_by_key.get(&key1), Some(BlobState::Ready(_))));
1117            assert!(matches!(state.blob_states_by_key.get(&key3), Some(BlobState::Ready(_))));
1118        }
1119
1120        // Re-open blob 2: it should generate a new key and open successfully
1121        let vmo2_new = pager_and_verifier.create_vmo(&hash2).await.expect("re-create vmo 2 failed");
1122        {
1123            let state = pager_and_verifier.cache.state.lock();
1124            let k = *state.keys_by_hash.get(&hash2).expect("hash2 missing after re-open");
1125            assert_ne!(k, key2);
1126            assert!(matches!(state.blob_states_by_key.get(&k), Some(BlobState::Ready(_))));
1127        }
1128
1129        drop(vmo1);
1130        drop(vmo3);
1131        drop(vmo2_new);
1132
1133        drop(pager_and_verifier);
1134        mapping_task.await;
1135        mapper_task.await;
1136    }
1137
1138    #[fuchsia::test]
1139    async fn test_cache_is_cleared_when_create_vmo_fails() {
1140        let (mapping_proxy, mut mapping_stream) =
1141            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1142        let (mapper_proxy, mut mapper_stream) =
1143            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1144
1145        let (mock_opened_tx, mock_opened_rx) = oneshot::channel();
1146        let mapping_task = fasync::Task::spawn(async move {
1147            let mut mock_opened_tx = Some(mock_opened_tx);
1148            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1149                mapping_stream.try_next().await.expect("try_next failed")
1150            {
1151                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1152                    .expect("zx::Vmo::create failed");
1153                responder.send(Ok(mapping_vmo)).expect("send failed");
1154                let mut session_stream = session.into_stream();
1155
1156                if let Some(fmapping::MappingSessionRequest::Open {
1157                    responder: _responder, ..
1158                }) = session_stream.try_next().await.expect("try_next failed")
1159                {
1160                    // Signal the primary test task that the session is inside Open
1161                    if let Some(tx) = mock_opened_tx.take() {
1162                        let _ = tx.send(());
1163                    }
1164
1165                    // Wait for test task to simulate failing create_vmo
1166                    let () = std::future::pending().await;
1167                }
1168            }
1169        });
1170
1171        let mapper_task = fasync::Task::spawn(async move {
1172            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1173                mapper_stream.try_next().await.expect("try_next failed")
1174            {
1175                responder.send(Ok(())).expect("send failed");
1176            }
1177        });
1178
1179        let pager_and_verifer = Arc::new(
1180            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1181                .await
1182                .expect("BlobPagerAndVerifier::new failed"),
1183        );
1184
1185        let blob_data = vec![0x42u8; TEST_BLOB_SIZE as usize];
1186        let (root, _) = fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
1187        let hash_val: [u8; 32] = root.into();
1188
1189        let verifier_clone = pager_and_verifer.clone();
1190
1191        let identifier = hash_val;
1192        let (abortable_future, abort_handle) = futures::future::abortable(async move {
1193            let _ = verifier_clone.create_vmo(&identifier).await;
1194        });
1195
1196        let identifier = &hash_val;
1197        let primary_task = fasync::Task::spawn(abortable_future);
1198
1199        // Wait until the mock mapping session catches the `Open` request and check that the cache
1200        // state is updated.
1201        mock_opened_rx.await.expect("Open exited abruptly");
1202        {
1203            let state = pager_and_verifer.cache.state.lock();
1204            assert_eq!(state.keys_by_hash.get(identifier), Some(&1));
1205            assert!(matches!(state.blob_states_by_key.get(&1), Some(BlobState::Opening { .. })));
1206        }
1207
1208        // Simulate a failed create_vmo (future abort drops execution context)
1209        abort_handle.abort();
1210        let _ = primary_task.await;
1211
1212        // The cache should be cleared after due to PendingEntry being dropped
1213        {
1214            let state = pager_and_verifer.cache.state.lock();
1215            assert!(state.keys_by_hash.get(identifier).is_none());
1216            assert!(state.blob_states_by_key.get(&1).is_none());
1217        }
1218
1219        drop(pager_and_verifer);
1220        drop(mapping_task);
1221        drop(mapper_task);
1222    }
1223
1224    #[fuchsia::test]
1225    async fn test_create_vmo_error_clears_cache_and_unblocks_waiters() {
1226        let (mapping_proxy, mut mapping_stream) =
1227            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1228        let (mapper_proxy, mut mapper_stream) =
1229            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1230
1231        let (open_received_tx, open_received_rx) = oneshot::channel();
1232        let (reply_tx, reply_rx) = oneshot::channel();
1233
1234        let mapping_task = fasync::Task::spawn(async move {
1235            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1236                mapping_stream.try_next().await.expect("try_next failed")
1237            {
1238                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1239                    .expect("zx::Vmo::create failed");
1240                responder.send(Ok(mapping_vmo)).expect("send failed");
1241                let mut session_stream = session.into_stream();
1242
1243                if let Some(fmapping::MappingSessionRequest::Open { responder, .. }) =
1244                    session_stream.try_next().await.expect("try_next failed")
1245                {
1246                    // Signal the test that the primary caller has entered Open and is Pending.
1247                    open_received_tx.send(()).expect("send open_received failed");
1248
1249                    // Await permission to reply with the error.
1250                    reply_rx.await.expect("reply_rx failed");
1251                    responder.send(Err(zx::Status::NOT_FOUND.into_raw())).expect("send failed");
1252                }
1253                // Channel closes when mapping_task finishes, causing subsequent requests to fail.
1254            }
1255        });
1256
1257        let mapper_task = fasync::Task::spawn(async move {
1258            if let Some(fblock::MapperRequest::OpenSession { responder, .. }) =
1259                mapper_stream.try_next().await.expect("try_next failed")
1260            {
1261                responder.send(Ok(())).expect("send failed");
1262            }
1263        });
1264
1265        let pager_and_verifier = Arc::new(
1266            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1267                .await
1268                .expect("BlobPagerAndVerifier::new failed"),
1269        );
1270
1271        let hash: [u8; 32] = [0x55; 32];
1272
1273        // Primary caller starts create_vmo.
1274        let verifier1 = pager_and_verifier.clone();
1275        let primary_future = fasync::Task::spawn(async move { verifier1.create_vmo(&hash).await });
1276
1277        // Wait until the primary caller is handling OpenSession (cache is Pending).
1278        open_received_rx.await.expect("open_received_rx failed");
1279        {
1280            let state = pager_and_verifier.cache.state.lock();
1281            assert!(matches!(
1282                state.keys_by_hash.get(&hash).and_then(|k| state.blob_states_by_key.get(k)),
1283                Some(BlobState::Opening { .. })
1284            ));
1285        }
1286
1287        // A second caller arrives while the primary creation is still pending.
1288        let verifier2 = pager_and_verifier.clone();
1289        let secondary_future =
1290            fasync::Task::spawn(async move { verifier2.create_vmo(&hash).await });
1291
1292        // Allow Fxfs to reply with an error to the primary caller.
1293        reply_tx.send(()).expect("reply_tx send failed");
1294
1295        // Both callers must return an error and complete without hanging on abandoned state, and
1296        // the primary caller must preserve the underlying zx::Status::NOT_FOUND.
1297        let primary_err = primary_future.await.expect_err("expected primary create_vmo to fail");
1298        assert_eq!(primary_err.downcast_ref::<zx::Status>(), Some(&zx::Status::NOT_FOUND));
1299        assert!(secondary_future.await.is_err());
1300
1301        // Cache should be empty.
1302        {
1303            let state = pager_and_verifier.cache.state.lock();
1304            assert!(state.keys_by_hash.get(&hash).is_none());
1305            assert!(state.blob_states_by_key.is_empty());
1306        }
1307
1308        drop(pager_and_verifier);
1309        drop(mapping_task);
1310        drop(mapper_task);
1311    }
1312
1313    #[fuchsia::test]
1314    async fn test_register_blob_arrives_before_open_completes() {
1315        // Test that if `RegisterBlob` arrives while `session.open().await` is still pending
1316        // (the arrival race), the blob's Merkle tree is retained and page reads succeed once
1317        // `create_vmo` completes.
1318        let (mapping_proxy, mut mapping_stream) =
1319            fidl::endpoints::create_proxy_and_stream::<fmapping::MappingProviderMarker>();
1320        let (mapper_proxy, mut mapper_stream) =
1321            fidl::endpoints::create_proxy_and_stream::<fblock::MapperMarker>();
1322
1323        let blob_data = vec![0x42u8; TEST_BLOB_SIZE as usize];
1324        let (root, leaf_hashes) =
1325            fuchsia_merkle::MerkleRootBuilder::new(Vec::new()).complete(&blob_data);
1326        let expected_hash: [u8; 32] = root.into();
1327
1328        let mut flat_leaves = Vec::new();
1329        for hash in &leaf_hashes {
1330            flat_leaves.extend_from_slice(hash.as_bytes());
1331        }
1332
1333        let (tx_delivery, rx_delivery) = oneshot::channel();
1334        let (open_key_tx, open_key_rx) = oneshot::channel();
1335        let (resume_open_tx, resume_open_rx) = oneshot::channel();
1336        let (close_tx, close_rx) = oneshot::channel();
1337
1338        let mapping_task = fasync::Task::spawn(async move {
1339            let mut close_tx = Some(close_tx);
1340            let mut open_key_tx = Some(open_key_tx);
1341            let mut resume_open_rx = Some(resume_open_rx);
1342            if let Some(fmapping::MappingProviderRequest::OpenSession { session, responder }) =
1343                mapping_stream.try_next().await.expect("try_next failed")
1344            {
1345                let mapping_vmo = zx::Vmo::create(zx::system_get_page_size().into())
1346                    .expect("zx::Vmo::create failed");
1347                responder.send(Ok(mapping_vmo)).expect("send failed");
1348
1349                let mut session_stream = session.into_stream();
1350                while let Some(request) = session_stream.try_next().await.expect("try_next failed")
1351                {
1352                    match request {
1353                        fmapping::MappingSessionRequest::Open { key, identifier, responder } => {
1354                            assert_eq!(identifier, expected_hash);
1355                            // Send key to test thread so it can deliver leaves before Open returns
1356                            if let Some(tx) = open_key_tx.take() {
1357                                tx.send(key).expect("open_key_tx failed");
1358                            }
1359                            if let Some(rx) = resume_open_rx.take() {
1360                                rx.await.expect("resume_open_rx failed");
1361                            }
1362                            responder.send(Ok(TEST_BLOB_SIZE)).expect("send failed");
1363                        }
1364                        fmapping::MappingSessionRequest::Close { key: _, responder } => {
1365                            responder.send(Ok(())).expect("send failed");
1366                            if let Some(tx) = close_tx.take() {
1367                                let _ = tx.send(());
1368                            }
1369                        }
1370                        _ => {}
1371                    }
1372                }
1373            }
1374        });
1375
1376        let mapper_task = fasync::Task::spawn(async move {
1377            if let Some(fblock::MapperRequest::OpenSession { delivery_queue, responder, .. }) =
1378                mapper_stream.try_next().await.expect("try_next failed")
1379            {
1380                tx_delivery.send(delivery_queue).expect("send failed");
1381                responder.send(Ok(())).expect("send failed");
1382            }
1383        });
1384
1385        let pager_and_verifier = Arc::new(
1386            BlobPagerAndVerifier::new(&mapping_proxy, &mapper_proxy)
1387                .await
1388                .expect("BlobPagerAndVerifier::new failed"),
1389        );
1390
1391        let delivery_vmo =
1392            rx_delivery.await.expect("rx_delivery wait failed").expect("delivery_vmo was None");
1393        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1394            delivery_vmo
1395                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1396                .expect("duplicate_handle failed"),
1397            zx::system_get_page_size() as usize,
1398            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1399        )
1400        .expect("SyncSender::new failed");
1401
1402        let verifier_clone = pager_and_verifier.clone();
1403        let create_task =
1404            fasync::Task::spawn(async move { verifier_clone.create_vmo(&expected_hash).await });
1405
1406        // Wait until `open()` is called and get the client-allocated key
1407        let key = open_key_rx.await.expect("open_key_rx failed");
1408
1409        // While `open()` is still pending, send `RegisterBlob`
1410        let mut payload =
1411            sender.reserve_payload(flat_leaves.len()).expect("reserve_payload failed");
1412        payload.data().copy_from_slice(&flat_leaves);
1413        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1414            key: key as u64,
1415            offset: payload.offset(),
1416            length: flat_leaves.len() as u32,
1417            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1418        }
1419        .into();
1420        payload.commit(raw_cmd).expect("commit failed");
1421
1422        // Wait until delivery thread processes `RegisterBlob` on the pending entry
1423        while !pager_and_verifier.cache.is_merkle_initialized(key) {
1424            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1425        }
1426
1427        // Resume Open and wait for `create_vmo` to finish
1428        resume_open_tx.send(()).expect("resume_open_tx failed");
1429        let vmo = create_task.await.expect("create_vmo failed");
1430
1431        // We should be able to deliver data and read it immediately
1432        let chunk = &blob_data[..TARGET_READ_AHEAD_SIZE];
1433        let mut data_payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
1434        data_payload.data().copy_from_slice(chunk);
1435        let data_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1436            key: key as u64,
1437            offset: data_payload.offset(),
1438            length: chunk.len() as u32,
1439            target_offset: 0,
1440        }
1441        .into();
1442        data_payload.commit(data_cmd).expect("commit failed");
1443
1444        let mut read_buf = vec![0u8; TARGET_READ_AHEAD_SIZE];
1445        vmo.read(&mut read_buf, 0).expect("read failed");
1446        assert_eq!(read_buf, chunk);
1447
1448        drop(vmo);
1449        close_rx.await.expect("close_rx failed");
1450
1451        drop(pager_and_verifier);
1452        mapping_task.await;
1453        mapper_task.await;
1454    }
1455
1456    #[fuchsia::test]
1457    async fn test_delivery_register_blob_verification() {
1458        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1459
1460        let _paged_vmo =
1461            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1462
1463        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1464            env.delivery_vmo
1465                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1466                .expect("duplicate_handle failed"),
1467            zx::system_get_page_size() as usize, // alignment of payload
1468            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1469        )
1470        .expect("SyncSender::new failed");
1471
1472        // Test corrupted leaves should fail register blob
1473        let mut corrupted_leaves = env.valid_leaves.clone();
1474        corrupted_leaves[0] ^= 0xFF;
1475
1476        let mut bad_payload =
1477            sender.reserve_payload(corrupted_leaves.len()).expect("reserve_payload failed");
1478        bad_payload.data().copy_from_slice(&corrupted_leaves);
1479
1480        let bad_raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1481            key: TEST_VMO_KEY,
1482            offset: bad_payload.offset(),
1483            length: corrupted_leaves.len() as u32,
1484            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1485        }
1486        .into();
1487        bad_payload.commit(bad_raw_cmd).expect("commit failed");
1488
1489        // Yield to allow background thread to process the invalid merkle
1490        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1491
1492        let blob =
1493            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1494
1495        // merkle_verifier should remain as None as the leaves are corrupted
1496        assert!(blob.merkle_verifier.get().is_none());
1497
1498        // Test with valid leaves
1499        let mut payload =
1500            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1501        payload.data().copy_from_slice(&env.valid_leaves);
1502
1503        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1504            key: TEST_VMO_KEY,
1505            offset: payload.offset(),
1506            length: env.valid_leaves.len() as u32,
1507            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1508        }
1509        .into();
1510        payload.commit(raw_cmd).expect("commit failed");
1511
1512        while blob.merkle_verifier.get().is_none() {
1513            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1514        }
1515
1516        drop(_paged_vmo);
1517        env.teardown().await;
1518    }
1519
1520    #[fuchsia::test]
1521    async fn test_register_blob_invalid_commands() {
1522        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1523
1524        let _paged_vmo =
1525            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1526
1527        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1528            env.delivery_vmo
1529                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1530                .expect("duplicate_handle failed"),
1531            std::mem::align_of::<RawDeliveryCommand>(),
1532            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1533        )
1534        .expect("SyncSender::new failed");
1535
1536        let blob =
1537            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1538
1539        // Test with unknown/expired key
1540        let mut invalid_key_payload =
1541            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1542        invalid_key_payload.data().copy_from_slice(&env.valid_leaves);
1543        let invalid_key_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1544            key: 9999 as u64,
1545            offset: invalid_key_payload.offset(),
1546            length: env.valid_leaves.len() as u32,
1547            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1548        }
1549        .into();
1550        invalid_key_payload.commit(invalid_key_cmd).expect("commit failed");
1551        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1552        assert!(blob.merkle_verifier.get().is_none());
1553
1554        // Test out-of-bounds offset/length
1555        let oob_payload = sender.reserve_payload(8).expect("reserve_payload failed");
1556        let oob_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1557            key: TEST_VMO_KEY,
1558            offset: u32::MAX - 4, // Malicious offset
1559            length: 8,
1560            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1561        }
1562        .into();
1563        oob_payload.commit(oob_cmd).expect("commit failed");
1564        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1565        assert!(blob.merkle_verifier.get().is_none());
1566
1567        // Test invalid leaf length (not a multiple of HASH_SIZE)
1568        let invalid_len_payload = sender.reserve_payload(10).expect("reserve_payload failed");
1569        let invalid_len_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1570            key: TEST_VMO_KEY,
1571            offset: invalid_len_payload.offset(),
1572            length: 10,
1573            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1574        }
1575        .into();
1576        invalid_len_payload.commit(invalid_len_cmd).expect("commit failed");
1577        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1578        assert!(blob.merkle_verifier.get().is_none());
1579
1580        drop(_paged_vmo);
1581        env.teardown().await;
1582    }
1583
1584    #[fuchsia::test]
1585    async fn test_delivery_data_supplies_pages() {
1586        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1587
1588        let paged_vmo =
1589            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1590
1591        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1592            env.delivery_vmo
1593                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1594                .expect("duplicate_handle failed"),
1595            zx::system_get_page_size() as usize, // alignment of payload
1596            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1597        )
1598        .expect("SyncSender::new failed");
1599
1600        let mut payload =
1601            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1602        payload.data().copy_from_slice(&env.valid_leaves);
1603        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1604            key: TEST_VMO_KEY,
1605            offset: payload.offset(),
1606            length: env.valid_leaves.len() as u32,
1607            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1608        }
1609        .into();
1610        payload.commit(raw_cmd).expect("commit failed");
1611
1612        let blob =
1613            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1614        while blob.merkle_verifier.get().is_none() {
1615            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1616        }
1617
1618        let test_payload = &env.blob_data[..TARGET_READ_AHEAD_SIZE];
1619
1620        let mut payload =
1621            sender.reserve_payload(test_payload.len()).expect("reserve_payload failed");
1622        payload.data().copy_from_slice(&test_payload);
1623
1624        let cmd: RawDeliveryCommand = DeliveryCommand::Data {
1625            key: TEST_VMO_KEY,
1626            target_offset: 0,
1627            length: test_payload.len() as u32,
1628            offset: payload.offset(),
1629        }
1630        .into();
1631        payload.commit(cmd).expect("commit failed");
1632
1633        let mut read_buf = vec![0u8; TARGET_READ_AHEAD_SIZE];
1634        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo failed");
1635        assert_eq!(read_buf, test_payload);
1636
1637        drop(paged_vmo);
1638        env.teardown().await;
1639    }
1640
1641    #[fuchsia::test]
1642    async fn test_delivery_data_corrupted() {
1643        // Verify that when a delivered chunk fails verification, client threads waiting
1644        // for data within that chunk receive `ZX_ERR_IO_DATA_INTEGRITY`.
1645        let blob_size = TARGET_READ_AHEAD_SIZE as u64;
1646        let env = TestEnv::new(blob_size).await;
1647
1648        let paged_vmo =
1649            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1650
1651        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1652            env.delivery_vmo
1653                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1654                .expect("duplicate_handle failed"),
1655            zx::system_get_page_size() as usize,
1656            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1657        )
1658        .expect("SyncSender::new failed");
1659
1660        let mut payload =
1661            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1662        payload.data().copy_from_slice(&env.valid_leaves);
1663        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1664            key: TEST_VMO_KEY,
1665            offset: payload.offset(),
1666            length: env.valid_leaves.len() as u32,
1667            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
1668        }
1669        .into();
1670        payload.commit(raw_cmd).expect("commit failed");
1671
1672        let blob =
1673            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1674        while blob.merkle_verifier.get().is_none() {
1675            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1676        }
1677
1678        // Prepare a corrupted 128 KiB chunk (the entire blob data).
1679        let mut corrupted_data = env.blob_data.clone();
1680        corrupted_data[0] ^= 0xFF;
1681
1682        let mut payload =
1683            sender.reserve_payload(corrupted_data.len()).expect("reserve_payload failed");
1684        payload.data().copy_from_slice(&corrupted_data);
1685        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1686            key: TEST_VMO_KEY,
1687            offset: payload.offset(),
1688            length: corrupted_data.len() as u32,
1689            target_offset: 0,
1690        }
1691        .into();
1692
1693        // Start client threads reading different pages within the delivery data chunk.
1694        // When verification fails, all waiting threads must fail with `IO_DATA_INTEGRITY`.
1695        let page_size = zx::system_get_page_size() as usize;
1696        let mut reader1 =
1697            spawn_reader_expect_error(&paged_vmo, 0, page_size, zx::Status::IO_DATA_INTEGRITY);
1698        let mut reader2 = spawn_reader_expect_error(
1699            &paged_vmo,
1700            page_size as u64,
1701            page_size,
1702            zx::Status::IO_DATA_INTEGRITY,
1703        );
1704
1705        // Wait for both client threads to start executing before giving them time to block on their
1706        // page faults.
1707        reader1.wait_started().await;
1708        reader2.wait_started().await;
1709        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1710
1711        // Commit the corrupt chunk. Verification will fail, causing the blocked `vmo.read()`
1712        // calls for that chunk to return `IO_DATA_INTEGRITY`.
1713        payload.commit(raw_cmd).expect("commit failed");
1714
1715        reader1.wait_and_verify().await;
1716        reader2.wait_and_verify().await;
1717
1718        drop(paged_vmo);
1719        env.teardown().await;
1720    }
1721
1722    #[fuchsia::test]
1723    async fn test_delivery_data_corrupted_chunk_preserves_supplied_pages() {
1724        let read_alignment = TARGET_READ_AHEAD_SIZE;
1725        let blob_size = (read_alignment * 2) as u64;
1726        let env = TestEnv::new(blob_size).await;
1727
1728        let paged_vmo =
1729            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1730
1731        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1732            env.delivery_vmo
1733                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1734                .expect("duplicate_handle failed"),
1735            zx::system_get_page_size() as usize,
1736            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1737        )
1738        .expect("SyncSender::new failed");
1739
1740        let mut payload =
1741            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1742        payload.data().copy_from_slice(&env.valid_leaves);
1743        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1744            key: TEST_VMO_KEY,
1745            offset: payload.offset(),
1746            length: env.valid_leaves.len() as u32,
1747            read_alignment: read_alignment as u32,
1748        }
1749        .into();
1750        payload.commit(raw_cmd).expect("commit failed");
1751
1752        let blob =
1753            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1754        while blob.merkle_verifier.get().is_none() {
1755            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1756        }
1757
1758        let page_size = zx::system_get_page_size() as usize;
1759
1760        // Chunk 1 test for successful page request.
1761        // Start a reader on page 0 and deliver the valid first chunk (0..read_alignment).
1762        let paged_vmo_clone1 =
1763            paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
1764        let (tx1, rx1) = oneshot::channel();
1765        let expected_page0 = env.blob_data[..page_size].to_vec();
1766        let thread1 = std::thread::spawn(move || {
1767            let mut buf = vec![0u8; page_size];
1768            paged_vmo_clone1.read(&mut buf, 0).expect("read should succeed");
1769            assert_eq!(buf, expected_page0);
1770            let _ = tx1.send(());
1771        });
1772        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1773
1774        let valid_chunk = &env.blob_data[..read_alignment];
1775        let mut payload =
1776            sender.reserve_payload(valid_chunk.len()).expect("reserve_payload failed");
1777        payload.data().copy_from_slice(valid_chunk);
1778        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1779            key: TEST_VMO_KEY,
1780            offset: payload.offset(),
1781            length: valid_chunk.len() as u32,
1782            target_offset: 0,
1783        }
1784        .into();
1785        payload.commit(raw_cmd).expect("commit failed");
1786
1787        rx1.await.expect("thread1 panicked or hung");
1788        thread1.join().expect("join failed");
1789
1790        // Test for failed page requests on the subsequent chunk.
1791        let mut reader2 = spawn_reader_expect_error(
1792            &paged_vmo,
1793            read_alignment as u64,
1794            page_size,
1795            zx::Status::IO_DATA_INTEGRITY,
1796        );
1797        reader2.wait_started().await;
1798        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1799
1800        // Deliver corrupted second chunk (read_alignment..read_alignment * 2).
1801        let mut corrupted_chunk = env.blob_data[read_alignment..].to_vec();
1802        corrupted_chunk[0] ^= 0xFF;
1803
1804        let mut payload =
1805            sender.reserve_payload(corrupted_chunk.len()).expect("reserve_payload failed");
1806        payload.data().copy_from_slice(&corrupted_chunk);
1807        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1808            key: TEST_VMO_KEY,
1809            offset: payload.offset(),
1810            length: corrupted_chunk.len() as u32,
1811            target_offset: read_alignment as u64,
1812        }
1813        .into();
1814        payload.commit(raw_cmd).expect("commit failed");
1815
1816        reader2.wait_and_verify().await;
1817
1818        // Verify that the previously supplied page 0 remains intact and readable.
1819        let mut buf = vec![0u8; page_size];
1820        paged_vmo.read(&mut buf, 0).expect("previously supplied page 0 must still be readable");
1821        assert_eq!(buf, env.blob_data[..page_size]);
1822
1823        drop(paged_vmo);
1824        env.teardown().await;
1825    }
1826
1827    #[fuchsia::test]
1828    async fn test_delivery_data_corrupted_multi_chunk_read() {
1829        let read_alignment = TARGET_READ_AHEAD_SIZE;
1830        let blob_size = (read_alignment * 3) as u64;
1831        let env = TestEnv::new(blob_size).await;
1832
1833        let paged_vmo =
1834            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1835
1836        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1837            env.delivery_vmo
1838                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1839                .expect("duplicate_handle failed"),
1840            zx::system_get_page_size() as usize,
1841            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1842        )
1843        .expect("SyncSender::new failed");
1844
1845        let mut payload =
1846            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1847        payload.data().copy_from_slice(&env.valid_leaves);
1848        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1849            key: TEST_VMO_KEY,
1850            offset: payload.offset(),
1851            length: env.valid_leaves.len() as u32,
1852            read_alignment: read_alignment as u32,
1853        }
1854        .into();
1855        payload.commit(raw_cmd).expect("commit failed");
1856
1857        let blob =
1858            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1859        while blob.merkle_verifier.get().is_none() {
1860            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1861        }
1862
1863        // A 384 KiB `vmo.read()` triggers a single page request spanning multiple pages.
1864        // Chunk 1 (0..128 KiB) is valid, Chunk 2 (128..256 KiB) is corrupted, and Chunk 3 is not
1865        // delivered because delivery stops on verification failure.
1866        // Verify that when Chunk 2 fails verification, the page request fails and
1867        // `vmo.read()` returns `IO_DATA_INTEGRITY`.
1868        let mut reader = spawn_reader_expect_error(
1869            &paged_vmo,
1870            0,
1871            blob_size as usize,
1872            zx::Status::IO_DATA_INTEGRITY,
1873        );
1874        reader.wait_started().await;
1875        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1876
1877        // Deliver Chunk 1 (0..128 KiB) valid.
1878        let valid_chunk1 = &env.blob_data[..read_alignment];
1879        let mut payload =
1880            sender.reserve_payload(valid_chunk1.len()).expect("reserve_payload failed");
1881        payload.data().copy_from_slice(valid_chunk1);
1882        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1883            key: TEST_VMO_KEY,
1884            offset: payload.offset(),
1885            length: valid_chunk1.len() as u32,
1886            target_offset: 0,
1887        }
1888        .into();
1889        payload.commit(raw_cmd).expect("commit failed");
1890
1891        // Allow the reader thread time to receive the supplied first chunk, copy it, and block on
1892        // the second chunk's unpopulated pages.
1893        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1894
1895        // Deliver Chunk 2 (128..256 KiB) corrupted.
1896        let mut corrupted_chunk2 = env.blob_data[read_alignment..read_alignment * 2].to_vec();
1897        corrupted_chunk2[0] ^= 0xFF;
1898        let mut payload =
1899            sender.reserve_payload(corrupted_chunk2.len()).expect("reserve_payload failed");
1900        payload.data().copy_from_slice(&corrupted_chunk2);
1901        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1902            key: TEST_VMO_KEY,
1903            offset: payload.offset(),
1904            length: corrupted_chunk2.len() as u32,
1905            target_offset: read_alignment as u64,
1906        }
1907        .into();
1908        payload.commit(raw_cmd).expect("commit failed");
1909
1910        // Don't deliver chunk 3 since chunk 2 failed.
1911
1912        // The `vmo.read()` must fail with IO_DATA_INTEGRITY.
1913        reader.wait_and_verify().await;
1914
1915        drop(paged_vmo);
1916        env.teardown().await;
1917    }
1918
1919    #[fuchsia::test]
1920    async fn test_delivery_data_uninitialized() {
1921        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1922
1923        let paged_vmo =
1924            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1925
1926        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1927            env.delivery_vmo
1928                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1929                .expect("duplicate_handle failed"),
1930            zx::system_get_page_size() as usize,
1931            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1932        )
1933        .expect("SyncSender::new failed");
1934
1935        // Spawn reader thread to generate the initial page request
1936        let mut reader = spawn_reader_expect_error(&paged_vmo, 0, 8192, zx::Status::BAD_STATE);
1937        reader.wait_started().await;
1938        fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1939
1940        // Intentionally push a Data chunk without sending RegisterBlob first
1941        let chunk = &env.blob_data[..8192];
1942        let mut payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
1943        payload.data().copy_from_slice(chunk);
1944        let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
1945            key: TEST_VMO_KEY,
1946            offset: payload.offset(),
1947            length: chunk.len() as u32,
1948            target_offset: 0,
1949        }
1950        .into();
1951        payload.commit(raw_cmd).expect("commit failed");
1952
1953        reader.wait_and_verify().await;
1954
1955        drop(paged_vmo);
1956        env.teardown().await;
1957    }
1958
1959    #[test_case(TARGET_READ_AHEAD_SIZE; "read_alignment_128k")]
1960    #[test_case(96 * 1024; "read_alignment_96k")]
1961    #[fuchsia::test]
1962    async fn test_delivery_data_multiple_chunks(read_alignment: usize) {
1963        let env = TestEnv::new(TEST_BLOB_SIZE).await;
1964
1965        let paged_vmo =
1966            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("create_vmo failed");
1967
1968        let mut sender = SyncSender::<RawDeliveryCommand>::new(
1969            env.delivery_vmo
1970                .duplicate_handle(zx::Rights::SAME_RIGHTS)
1971                .expect("duplicate_handle failed"),
1972            zx::system_get_page_size() as usize,
1973            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
1974        )
1975        .expect("SyncSender::new failed");
1976
1977        let mut payload =
1978            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
1979        payload.data().copy_from_slice(&env.valid_leaves);
1980        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
1981            key: TEST_VMO_KEY,
1982            offset: payload.offset(),
1983            length: env.valid_leaves.len() as u32,
1984            read_alignment: read_alignment as u32,
1985        }
1986        .into();
1987        payload.commit(raw_cmd).expect("commit failed");
1988
1989        let blob =
1990            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
1991        while blob.merkle_verifier.get().is_none() {
1992            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
1993        }
1994
1995        let expected_data = env.blob_data.clone();
1996        let paged_vmo_clone =
1997            paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).expect("duplicate_handle failed");
1998
1999        let (tx, rx) = oneshot::channel();
2000        let thread = std::thread::spawn(move || {
2001            let mut buf = vec![0u8; expected_data.len()];
2002            paged_vmo_clone.read(&mut buf, 0).expect("failed to read from paged vmo");
2003            assert_eq!(buf, expected_data);
2004            let _ = tx.send(());
2005        });
2006
2007        // Push incrementally - chunks must be a multiple of the blob's read_alignment
2008        for (i, chunk) in env.blob_data.chunks(read_alignment).enumerate() {
2009            let mut payload = sender.reserve_payload(chunk.len()).expect("reserve_payload failed");
2010            payload.data().copy_from_slice(chunk);
2011            let raw_cmd: RawDeliveryCommand = DeliveryCommand::Data {
2012                key: TEST_VMO_KEY,
2013                offset: payload.offset(),
2014                length: chunk.len() as u32,
2015                target_offset: (i * read_alignment) as u64,
2016            }
2017            .into();
2018            payload.commit(raw_cmd).expect("commit failed");
2019        }
2020
2021        rx.await.expect("reading thread panicked or hung");
2022        thread.join().expect("join failed");
2023
2024        drop(paged_vmo);
2025        env.teardown().await;
2026    }
2027
2028    #[fuchsia::test]
2029    async fn test_delivery_data_unaligned_blob_size() {
2030        let data_size = (TARGET_READ_AHEAD_SIZE + 1024) as u64; // Blob with unaligned size
2031        let env = TestEnv::new(data_size).await;
2032
2033        let paged_vmo =
2034            env.pager_and_verifier.create_vmo(&env.valid_root).await.expect("Failed to create VMO");
2035
2036        let mut sender = SyncSender::<RawDeliveryCommand>::new(
2037            env.delivery_vmo
2038                .duplicate_handle(zx::Rights::SAME_RIGHTS)
2039                .expect("duplicate_handle failed"),
2040            zx::system_get_page_size() as usize, // alignment of payload
2041            mapping::PENDING_DELIVERY_COMMANDS_CAPACITY,
2042        )
2043        .expect("SyncSender::new failed");
2044
2045        let mut payload =
2046            sender.reserve_payload(env.valid_leaves.len()).expect("reserve_payload failed");
2047        payload.data().copy_from_slice(&env.valid_leaves);
2048        let raw_cmd: RawDeliveryCommand = DeliveryCommand::RegisterBlob {
2049            key: TEST_VMO_KEY,
2050            offset: payload.offset(),
2051            length: env.valid_leaves.len() as u32,
2052            read_alignment: TARGET_READ_AHEAD_SIZE as u32,
2053        }
2054        .into();
2055        payload.commit(raw_cmd).expect("commit failed");
2056
2057        let blob =
2058            env.pager_and_verifier.cache.get_by_key(TEST_VMO_KEY).expect("get_by_key failed");
2059        while blob.merkle_verifier.get().is_none() {
2060            fasync::Timer::new(std::time::Duration::from_millis(5)).await;
2061        }
2062
2063        let test_payload = env.blob_data.clone();
2064        assert_eq!(test_payload.len(), data_size as usize);
2065
2066        let chunk1 = &test_payload[..TARGET_READ_AHEAD_SIZE];
2067        let mut payload1 = sender.reserve_payload(chunk1.len()).expect("reserve_payload failed");
2068        payload1.data().copy_from_slice(chunk1);
2069        let off1 = payload1.offset();
2070        payload1
2071            .commit(
2072                DeliveryCommand::Data {
2073                    key: TEST_VMO_KEY,
2074                    target_offset: 0,
2075                    length: chunk1.len() as u32,
2076                    offset: off1,
2077                }
2078                .into(),
2079            )
2080            .expect("commit failed");
2081
2082        let chunk2 = &test_payload[TARGET_READ_AHEAD_SIZE..];
2083        let page_size = zx::system_get_page_size() as usize;
2084        let chunk2_len_aligned = chunk2.len().div_ceil(page_size) * page_size;
2085        let mut payload2 =
2086            sender.reserve_payload(chunk2_len_aligned).expect("reserve_payload failed");
2087        let payload2_data = payload2.data();
2088        payload2_data.subslice_mut(0..chunk2.len()).copy_from_slice(chunk2);
2089        payload2_data.subslice_mut(chunk2.len()..chunk2_len_aligned).fill(0);
2090
2091        let off2 = payload2.offset();
2092        payload2
2093            .commit(
2094                DeliveryCommand::Data {
2095                    key: TEST_VMO_KEY,
2096                    target_offset: TARGET_READ_AHEAD_SIZE as u64,
2097                    length: chunk2_len_aligned as u32,
2098                    offset: off2,
2099                }
2100                .into(),
2101            )
2102            .expect("commit failed");
2103
2104        let mut read_buf = vec![0u8; data_size as usize];
2105        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo failed");
2106        assert_eq!(read_buf, test_payload);
2107
2108        let page_size = zx::system_get_page_size() as u64;
2109        // Round the unaligned data size to the next page boundary
2110        let out_of_bounds_offset = data_size.div_ceil(page_size) * page_size;
2111        // Attempt to read from an out of bounds offset
2112        let mut read_buf = vec![0u8; 1];
2113        let res = paged_vmo.read(&mut read_buf, out_of_bounds_offset);
2114        assert_eq!(res, Err(zx::Status::OUT_OF_RANGE));
2115
2116        drop(paged_vmo);
2117        env.teardown().await;
2118    }
2119}