Skip to main content

fxfs_platform_testing/fuchsia/fxblob/
blob.rs

1// Copyright 2023 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//! This module contains the [`FxBlob`] node type used to represent an immutable blob persisted to
6//! disk which can be read back.
7
8use crate::constants::*;
9use crate::fuchsia::directory::FxDirectory;
10use crate::fuchsia::errors::map_to_status;
11use crate::fuchsia::node::{FxNode, OpenedNode};
12use crate::fuchsia::pager::{
13    MarkDirtyRange, PageInRange, PagerBacked, PagerPacketReceiverRegistration, default_page_in,
14};
15use crate::fuchsia::volume::{FxVolume, READ_AHEAD_SIZE};
16use anyhow::{Context, Error, anyhow, bail, ensure};
17use delivery_blob::compression::{CompressionAlgorithm, CompressionInfo};
18use fidl_fuchsia_feedback::{Annotation, Attachment, CrashReport};
19use fidl_fuchsia_mem::Buffer as MemBuffer;
20use fuchsia_component_client::connect_to_protocol;
21use fuchsia_merkle::{Hash, MerkleVerifier, ReadSizedMerkleVerifier};
22use futures::{StreamExt, TryStreamExt, try_join};
23use fxfs::blob_metadata::{BlobFormat, BlobMetadata, FxfsBlobMetadataExt};
24use fxfs::errors::FxfsError;
25use fxfs::lock_keys;
26use fxfs::log::*;
27use fxfs::object_handle::ObjectHandle;
28use fxfs::object_store::transaction::LockKey;
29use fxfs::object_store::{AttributeId, DataObjectHandle, ObjectDescriptor, StoreObjectHandle};
30use fxfs::round::round_down;
31use fxfs_macros::ToWeakNode;
32use mapping::Extent as MappingExtent;
33use refaults_vmo::AtomicBitVec;
34use std::ops::Range;
35use std::sync::Arc;
36use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
37use storage_device::buffer::{Buffer, BufferFuture, MutableBufferRef};
38use storage_units::page_size;
39use zx::Status;
40
41/// The extent mappings for a blob's data and merkle attributes.
42///
43/// Each `MappingExtent` maps a logical byte range to a physical device offset.
44/// Extents are sorted by logical offset and non-overlapping.
45pub struct BlobExtents {
46    pub data: Vec<MappingExtent>,
47    pub merkle: Vec<MappingExtent>,
48}
49
50// When the top bit of the open count is set, it means the file has been deleted and when the count
51// drops to zero, it will be tombstoned.  Once it has dropped to zero, it cannot be opened again
52// (assertions will fire).
53const PURGED: usize = 1 << (usize::BITS - 1);
54
55const EMPTY_BLOB_HASH: Hash = Hash::from_array([
56    0x15, 0xec, 0x7b, 0xf0, 0xb5, 0x07, 0x32, 0xb4, 0x9f, 0x82, 0x28, 0xe0, 0x7d, 0x24, 0x36, 0x53,
57    0x38, 0xf9, 0xe3, 0xab, 0x99, 0x4b, 0x00, 0xaf, 0x08, 0xe5, 0xa3, 0xbf, 0xfe, 0x55, 0xfd, 0x8b,
58]);
59
60/// State for serving page requests via Fxfs's internal pager.
61///
62/// Only populated when `use_driver_blob_paging` is false; when driver blob paging is enabled,
63/// `pkg-cache` creates and pages the blob VMOs directly via the block driver mapper.
64struct FxBlobPagerState {
65    vmo: zx::Vmo,
66    merkle_verifier: ReadSizedMerkleVerifier,
67    compression_info: Option<CompressionInfo>,
68    pager_packet_receiver_registration: Arc<PagerPacketReceiverRegistration<FxBlob>>,
69    chunks_supplied: AtomicBitVec,
70}
71
72/// Represents an immutable blob stored on Fxfs with associated an merkle tree.
73#[derive(ToWeakNode)]
74pub struct FxBlob {
75    handle: StoreObjectHandle<FxVolume>,
76    open_count: AtomicUsize,
77    merkle_root: Hash,
78    uncompressed_size: u64, // always set.
79    pager_state: Option<FxBlobPagerState>,
80}
81
82// Fuchsia can have many open blobs at once. The size of FxBlob is important.
83static_assertions::const_assert!(size_of::<FxBlob>() <= 192);
84
85impl FxBlob {
86    pub async fn new(
87        handle: StoreObjectHandle<FxVolume>,
88        merkle_root: Hash,
89    ) -> Result<Arc<Self>, Error> {
90        let stored_size =
91            handle.store().get_attribute_size(handle.object_id(), AttributeId::DATA).await?;
92        let metadata = BlobMetadata::read_from(&handle).await?;
93        let uncompressed_size = match &metadata.format {
94            BlobFormat::Uncompressed => stored_size,
95            BlobFormat::ChunkedZstd { uncompressed_size, .. }
96            | BlobFormat::ChunkedLz4 { uncompressed_size, .. } => *uncompressed_size,
97        };
98
99        // Check the uncompressed size of the blob against the number of merkle leaves. This only
100        // ensures that the uncompressed size lands within the same merkle block as the true
101        // uncompressed size. To fully validate the uncompressed size requires reading in the last
102        // merkle block and verifying it.
103        let expect_merkle_hashes = if uncompressed_size <= fuchsia_merkle::BLOCK_SIZE as u64 {
104            0
105        } else {
106            uncompressed_size.div_ceil(fuchsia_merkle::BLOCK_SIZE as u64)
107        };
108        ensure!(
109            metadata.merkle_leaves.len() as u64 == expect_merkle_hashes,
110            FxfsError::IntegrityError
111        );
112        // Fully validate the empty blob and its size.
113        ensure!(
114            (uncompressed_size == 0) == (merkle_root == EMPTY_BLOB_HASH),
115            FxfsError::IntegrityError
116        );
117
118        if handle.owner().use_driver_blob_paging() {
119            return Ok(Arc::new(Self {
120                handle,
121                open_count: AtomicUsize::new(0),
122                merkle_root,
123                uncompressed_size,
124                pager_state: None,
125            }));
126        }
127
128        let compression_info = match &metadata.format {
129            BlobFormat::Uncompressed => None,
130            BlobFormat::ChunkedZstd { chunk_size, compressed_offsets, .. } => {
131                Some(CompressionInfo::new(
132                    *chunk_size,
133                    stored_size,
134                    compressed_offsets,
135                    CompressionAlgorithm::Zstd,
136                )?)
137            }
138            BlobFormat::ChunkedLz4 { chunk_size, compressed_offsets, .. } => {
139                Some(CompressionInfo::new(
140                    *chunk_size,
141                    stored_size,
142                    compressed_offsets,
143                    CompressionAlgorithm::Lz4,
144                )?)
145            }
146        };
147        let merkle_verifier = metadata.into_merkle_verifier(merkle_root)?;
148
149        let min_chunk_size = min_chunk_size(&compression_info);
150        let merkle_verifier =
151            ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize)?;
152        let chunks_supplied = AtomicBitVec::new(uncompressed_size.div_ceil(min_chunk_size));
153
154        Ok(Arc::new_cyclic(|weak| {
155            let (vmo, pager_packet_receiver_registration) = handle
156                .owner()
157                .pager()
158                .create_vmo(weak.clone(), uncompressed_size, zx::VmoOptions::empty())
159                .unwrap();
160            set_vmo_name(&vmo, &merkle_root);
161            Self {
162                handle,
163                open_count: AtomicUsize::new(0),
164                merkle_root,
165                uncompressed_size,
166                pager_state: Some(FxBlobPagerState {
167                    vmo,
168                    merkle_verifier,
169                    compression_info,
170                    pager_packet_receiver_registration: Arc::new(
171                        pager_packet_receiver_registration,
172                    ),
173                    chunks_supplied,
174                }),
175            }
176        }))
177    }
178
179    /// Returns the new blob.
180    pub fn overwrite_me(
181        self: &Arc<Self>,
182        handle: DataObjectHandle<FxVolume>,
183        // The Merkle verifier and optional compression metadata needed to initialise the new
184        // blob's `FxBlobPagerState` when Fxfs acts as the pager, or `None` when driver blob paging
185        // is enabled.
186        pager_info: Option<(MerkleVerifier, Option<CompressionInfo>)>,
187    ) -> Arc<Self> {
188        let pager_state =
189            if let (Some(old_pager_state), Some((merkle_verifier, compression_info))) =
190                (&self.pager_state, pager_info)
191            {
192                let min_chunk_size = min_chunk_size(&compression_info);
193                let merkle_verifier =
194                    ReadSizedMerkleVerifier::new(merkle_verifier, min_chunk_size as usize).expect(
195                        "The chunk size should have been validated by the delivery blob parser",
196                    );
197                // The chunk size may have changed between the old blob and the new blob.
198                // Preserving the chunks supplied bits isn't important.
199                let chunks_supplied =
200                    AtomicBitVec::new(self.uncompressed_size.div_ceil(min_chunk_size));
201                let vmo = old_pager_state.vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
202
203                Some(FxBlobPagerState {
204                    vmo,
205                    merkle_verifier,
206                    compression_info,
207                    pager_packet_receiver_registration: old_pager_state
208                        .pager_packet_receiver_registration
209                        .clone(),
210                    chunks_supplied,
211                })
212            } else {
213                None
214            };
215
216        let new_blob = Arc::new(Self {
217            handle: handle.into_store_object_handle(),
218            open_count: AtomicUsize::new(0),
219            merkle_root: self.merkle_root,
220            uncompressed_size: self.uncompressed_size,
221            pager_state,
222        });
223
224        // We have tests that rely on the cache being purged and there are races where the
225        // `FxBlob::drop` isn't called early enough, which can make the test flaky.
226        self.handle.owner().cache().remove(self.as_ref());
227
228        if let Some(old_pager_state) = &self.pager_state {
229            // Lock must be held until the open counts is incremented to prevent concurrent handling
230            // of zero children signals.
231            let receiver_lock = old_pager_state
232                .pager_packet_receiver_registration
233                .receiver()
234                .set_receiver(&new_blob);
235            if receiver_lock.is_strong() {
236                // If there was a strong moved between them, then the counts exchange as well. It is
237                // only important that the increment happen under the lock as it may handle the next
238                // zero children signal, no new requests can now go to the old blob, and because
239                // existing requests hold an open count reference using `try_keep_open` for the
240                // duration of the request, we can immediately decrement the open count of the old
241                // blob.
242                new_blob.open_count_add_one();
243                self.clone().open_count_sub_one();
244            }
245        }
246        new_blob
247    }
248
249    pub fn root(&self) -> Hash {
250        self.merkle_root
251    }
252
253    pub fn byte_size(&self) -> u64 {
254        self.uncompressed_size
255    }
256
257    pub fn has_fxfs_pager(&self) -> bool {
258        self.pager_state.is_some()
259    }
260
261    fn pager_state(&self) -> &FxBlobPagerState {
262        self.pager_state
263            .as_ref()
264            .expect("FxBlob pager state not initialized when use_driver_blob_paging is enabled")
265    }
266
267    /// Returns the extents mapping logical offsets to device offsets for both merkle and data
268    /// attributes. Each `MappingExtent` maps a logical byte range to a physical device offset.
269    /// Extents are sorted by logical offset and non-overlapping.
270    pub async fn get_mapping_extents(&self) -> Result<BlobExtents, Error> {
271        let tree = &self.handle.store().tree();
272        let layer_set = tree.layer_set();
273
274        // Query data extents
275        let mut merger = layer_set.merger();
276        let stream = self.handle.extent_stream(&mut merger, AttributeId::DATA).await?;
277        // TODO(https://fxbug.dev/535489428): Remove use of vec here.
278        let data = stream
279            .map(|res| {
280                let extent = res?;
281                MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
282                    .map_err(|_| anyhow!(FxfsError::Inconsistent))
283            })
284            .try_collect()
285            .await?;
286
287        // Query Merkle extents
288        let mut merger = layer_set.merger();
289        let stream = self.handle.extent_stream(&mut merger, AttributeId::BLOB_METADATA).await?;
290        let merkle = stream
291            .map(|res| {
292                let extent = res?;
293                MappingExtent::try_new(extent.logical_range(), Some(extent.device_range().start))
294                    .map_err(|_| anyhow!(FxfsError::Inconsistent))
295            })
296            .try_collect()
297            .await?;
298
299        Ok(BlobExtents { data, merkle })
300    }
301
302    /// Returns the stored (on-disk) byte size of the blob data attribute.
303    pub async fn stored_size(&self) -> Result<u64, Error> {
304        self.handle.store().get_attribute_size(self.handle.object_id(), AttributeId::DATA).await
305    }
306
307    fn record_page_fault_metric(&self, range: &Range<u64>) {
308        let pager_state = self.pager_state();
309        let page_size = page_size();
310        let total_pages = (range.end - range.start) / page_size;
311        if total_pages > 0 {
312            self.handle
313                .owner()
314                .page_fault_counter()
315                .increment_faults(total_pages, Ordering::Relaxed);
316        }
317
318        let chunk_size: u64 = min_chunk_size(&pager_state.compression_info);
319        let pages_per_chunk = chunk_size / page_size;
320
321        let first_chunk = range.start / chunk_size;
322        let full_end_chunk = range.end / chunk_size;
323
324        let mut supplied_pages =
325            pager_state.chunks_supplied.test_and_set_range(first_chunk, full_end_chunk)
326                * pages_per_chunk;
327
328        // The end of the range may not be chunk aligned if it's the last chunk.
329        let trailing_pages = (range.end % chunk_size) / page_size;
330        if trailing_pages > 0 {
331            supplied_pages +=
332                pager_state.chunks_supplied.test_and_set_range(full_end_chunk, full_end_chunk + 1)
333                    * trailing_pages;
334        }
335
336        if supplied_pages > 0 {
337            self.handle
338                .owner()
339                .page_fault_counter()
340                .increment_refaults(supplied_pages, Ordering::Relaxed);
341        }
342    }
343
344    /// Allocates a device buffer for use with hardware/driver reads.
345    fn allocate_buffer(&self, size: u64) -> BufferFuture<'_> {
346        self.handle.store().device().allocate_buffer(size as usize)
347    }
348
349    /// Allocates a trusted buffer that no other processes or drivers can access,
350    /// suitable for zero-copy decompression and Merkle tree verification.
351    fn allocate_trusted_buffer(&self, size: u64) -> BufferFuture<'_> {
352        self.handle.owner().blob_allocator().allocate_buffer(size as usize)
353    }
354
355    async fn read_blocks(&self, offset: u64, buf: MutableBufferRef<'_>) -> Result<(), Error> {
356        let fs = self.handle.store().filesystem();
357        let guard = fs
358            .lock_manager()
359            .read_lock(lock_keys![LockKey::object_attribute(
360                self.handle.store().store_object_id(),
361                self.handle.object_id(),
362                AttributeId::DATA,
363            )])
364            .await;
365        self.handle.read_aligned_unchecked(AttributeId::DATA, offset, buf, &guard).await
366    }
367}
368
369impl Drop for FxBlob {
370    fn drop(&mut self) {
371        let volume = self.handle.owner();
372        volume.cache().remove(self);
373    }
374}
375
376impl OpenedNode<FxBlob> {
377    /// Creates a read-only child VMO for this blob backed by the pager. The blob cannot be purged
378    /// until all child VMOs have been destroyed.
379    ///
380    /// Returns [`Status::NOT_SUPPORTED`] when driver blob paging is enabled on the volume.
381    ///
382    /// *WARNING*: We need to ensure the open count is non-zero before invoking this function, so
383    /// it is only implemented for [`OpenedNode<FxBlob>`]. This prevents the blob from being purged
384    /// before we get a chance to register it with the pager for [`zx::Signals::VMO_ZERO_CHILDREN`].
385    pub fn create_child_vmo(&self) -> Result<zx::Vmo, Status> {
386        let blob = self.0.as_ref();
387        let pager_state = blob.pager_state.as_ref().ok_or(Status::NOT_SUPPORTED)?;
388        let child_vmo = pager_state.vmo.create_child(
389            zx::VmoChildOptions::REFERENCE | zx::VmoChildOptions::NO_WRITE,
390            0,
391            0,
392        )?;
393        if blob.handle.owner().pager().watch_for_zero_children(blob).map_err(map_to_status)? {
394            // Take an open count so that we keep this object alive if it is otherwise closed. This
395            // is only valid since we know the current open count is non-zero, otherwise we might
396            // increment the open count after the blob has been purged.
397            blob.open_count_add_one();
398        }
399        Ok(child_vmo)
400    }
401}
402
403impl FxNode for FxBlob {
404    fn object_id(&self) -> u64 {
405        self.handle.object_id()
406    }
407
408    fn parent(&self) -> Option<Arc<FxDirectory>> {
409        unreachable!(); // Add a parent back-reference if needed.
410    }
411
412    fn set_parent(&self, _parent: Arc<FxDirectory>) {
413        // NOP
414    }
415
416    fn open_count_add_one(&self) {
417        let old = self.open_count.fetch_add(1, Ordering::Relaxed);
418        assert!(old != PURGED && old != PURGED - 1);
419    }
420
421    fn open_count_sub_one(self: Arc<Self>) {
422        let old = self.open_count.fetch_sub(1, Ordering::Relaxed);
423        assert!(old & !PURGED > 0);
424        if old == PURGED + 1 {
425            let store = self.handle.store();
426            store
427                .filesystem()
428                .ensure_local()
429                .graveyard()
430                .queue_tombstone_object(store.store_object_id(), self.object_id());
431        }
432    }
433
434    fn object_descriptor(&self) -> ObjectDescriptor {
435        ObjectDescriptor::File
436    }
437
438    fn terminate(&self) {
439        if let Some(pager_state) = &self.pager_state {
440            pager_state.pager_packet_receiver_registration.stop_watching_for_zero_children();
441        }
442    }
443
444    fn mark_to_be_purged(self: Arc<Self>) {
445        let old = self.open_count.fetch_or(PURGED, Ordering::Relaxed);
446        assert!(old & PURGED == 0);
447        if old == 0 {
448            let store = self.handle.store();
449            store
450                .filesystem()
451                .ensure_local()
452                .graveyard()
453                .queue_tombstone_object(store.store_object_id(), self.object_id());
454        }
455    }
456}
457
458impl PagerBacked for FxBlob {
459    fn try_keep_open(self: Arc<Self>) -> Result<OpenedNode<Self>, Arc<Self>> {
460        let mut old = self.open_count.load(Ordering::Relaxed);
461        loop {
462            if old & !PURGED == 0 {
463                return Err(self);
464            }
465            assert!(old & !PURGED < PURGED - 1);
466            match self.open_count.compare_exchange_weak(
467                old,
468                old + 1,
469                Ordering::Relaxed,
470                Ordering::Relaxed,
471            ) {
472                Ok(_) => return Ok(OpenedNode(self)),
473                Err(new_value) => old = new_value,
474            }
475        }
476    }
477
478    fn pager(&self) -> &crate::pager::Pager {
479        self.handle.owner().pager()
480    }
481
482    fn pager_packet_receiver_registration(&self) -> &PagerPacketReceiverRegistration<Self> {
483        &self.pager_state().pager_packet_receiver_registration
484    }
485
486    fn vmo(&self) -> &zx::Vmo {
487        &self.pager_state().vmo
488    }
489
490    fn page_in(self: Arc<Self>, range: PageInRange<Self>) {
491        let read_ahead_size = if let Some(compression_info) = &self.pager_state().compression_info {
492            read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
493        } else {
494            READ_AHEAD_SIZE
495        };
496        // Delegate to the generic page handling code.
497        default_page_in(self, range, read_ahead_size)
498    }
499
500    fn mark_dirty(self: Arc<Self>, _range: MarkDirtyRange<Self>) {
501        unreachable!();
502    }
503
504    fn on_zero_children(self: Arc<Self>) {
505        self.open_count_sub_one();
506    }
507
508    fn byte_size(&self) -> u64 {
509        self.uncompressed_size
510    }
511
512    async fn aligned_read(&self, range: Range<u64>) -> Result<Buffer<'_>, Error> {
513        // The vmo shouldn't have full pages beyond the end of the blob so we shouldn't be getting
514        // page faults for ranges beyond the end of the blob.
515        ensure!(range.start < self.uncompressed_size, FxfsError::InvalidArgs);
516        self.record_page_fault_metric(&range);
517
518        let pager_state = self.pager_state();
519        let mut buffer = match pager_state.compression_info {
520            Some(_) => self.allocate_trusted_buffer(range.end - range.start).await,
521            None => self.allocate_buffer(range.end - range.start).await,
522        };
523        let unaligned_bytes =
524            (std::cmp::min(range.end, self.uncompressed_size) - range.start) as usize;
525        match &pager_state.compression_info {
526            None => self.read_blocks(range.start, buffer.as_mut()).await?,
527            Some(compression_info) => {
528                let compressed_offsets =
529                    compression_info.compressed_range_for_uncompressed_range(&range)?;
530                let bs = self.handle.block_size();
531                let aligned = bs.align_range_outwards(&compressed_offsets).unwrap();
532                let mut compressed_buf = self.allocate_buffer(aligned.end - aligned.start).await;
533
534                let mut decompression_errors = 0;
535                loop {
536                    try_join!(self.read_blocks(aligned.start, compressed_buf.as_mut()), async {
537                        buffer
538                            .allocator()
539                            .buffer_source()
540                            .commit_range(buffer.range())
541                            .map_err(|e| e.into())
542                    })
543                    .with_context(|| {
544                        format!(
545                            "Failed to read compressed range {:?}, len {}",
546                            aligned,
547                            compression_info.compressed_size()
548                        )
549                    })?;
550                    let compressed_buf_range = (compressed_offsets.start - aligned.start) as usize
551                        ..(compressed_offsets.end - aligned.start) as usize;
552
553                    let decompression_result = {
554                        fxfs_trace::duration!("blob-decompress", "len" => unaligned_bytes);
555                        compression_info.decompress(
556                            compressed_buf.as_ptr_slice().subslice(compressed_buf_range),
557                            buffer.subslice_mut(0..unaligned_bytes).try_as_mut_slice().unwrap(),
558                            range.start,
559                        )
560                    };
561                    match decompression_result {
562                        Ok(()) => break,
563                        Err(error) => {
564                            record_decompression_error_crash_report(
565                                &compressed_buf.to_vec(),
566                                &range,
567                                &compressed_offsets,
568                                &self.merkle_root,
569                            )
570                            .await;
571                            decompression_errors += 1;
572                            if decompression_errors == 2 {
573                                bail!(
574                                    anyhow!(FxfsError::IntegrityError)
575                                        .context(format!("Decompression error: {error:?}"))
576                                );
577                            } else {
578                                warn!(error:?; "Decompression error; retrying");
579                            }
580                        }
581                    }
582                } // loop
583                if decompression_errors > 0 {
584                    info!("Read succeeded on second attempt");
585                }
586            }
587        };
588        // Zero the tail before verification.
589        buffer.subslice_mut(unaligned_bytes..buffer.len()).fill(0);
590        {
591            // TODO(https://fxbug.dev/42073035): This should be offloaded to the kernel at which
592            // point we can delete this.
593            fxfs_trace::duration!("blob-verify", "len" => unaligned_bytes);
594            pager_state.merkle_verifier.verify_aligned(
595                range.start as usize,
596                buffer.as_ptr_slice().subslice(0..buffer.len()),
597                unaligned_bytes,
598            )?;
599        }
600        Ok(buffer)
601    }
602}
603
604fn set_vmo_name(vmo: &zx::Vmo, merkle_root: &Hash) {
605    let trimmed_merkle = &merkle_root.to_string()[0..BLOB_NAME_HASH_LENGTH];
606    let name = format!("{BLOB_NAME_PREFIX}{trimmed_merkle}");
607    let name = zx::Name::new(&name).unwrap();
608    vmo.set_name(&name).unwrap();
609}
610
611fn min_chunk_size(compression_info: &Option<CompressionInfo>) -> u64 {
612    if let Some(compression_info) = compression_info {
613        read_ahead_size_for_chunk_size(compression_info.chunk_size(), READ_AHEAD_SIZE)
614    } else {
615        READ_AHEAD_SIZE
616    }
617}
618
619fn read_ahead_size_for_chunk_size(chunk_size: u64, suggested_read_ahead_size: u64) -> u64 {
620    if chunk_size >= suggested_read_ahead_size {
621        chunk_size
622    } else {
623        round_down(suggested_read_ahead_size, chunk_size)
624    }
625}
626
627async fn record_decompression_error_crash_report(
628    compressed_buf: &[u8],
629    uncompressed_offsets: &Range<u64>,
630    compressed_offsets: &Range<u64>,
631    merkle_root: &Hash,
632) {
633    static DONE_ONCE: AtomicBool = AtomicBool::new(false);
634    if !DONE_ONCE.swap(true, Ordering::Relaxed) {
635        if let Ok(proxy) = connect_to_protocol::<fidl_fuchsia_feedback::CrashReporterMarker>() {
636            let size = compressed_buf.len() as u64;
637            let vmo = zx::Vmo::create(size).unwrap();
638            vmo.write(compressed_buf, 0).unwrap();
639            if let Err(e) = proxy
640                .file_report(CrashReport {
641                    program_name: Some("fxfs".to_string()),
642                    crash_signature: Some("fuchsia-fxfs-decompression_error".to_string()),
643                    is_fatal: Some(false),
644                    annotations: Some(vec![
645                        Annotation {
646                            key: "fxfs.range".to_string(),
647                            value: format!("{:?}", uncompressed_offsets),
648                        },
649                        Annotation {
650                            key: "fxfs.compressed_offsets".to_string(),
651                            value: format!("{:?}", compressed_offsets),
652                        },
653                        Annotation {
654                            key: "fxfs.merkle_root".to_string(),
655                            value: format!("{}", merkle_root),
656                        },
657                    ]),
658                    attachments: Some(vec![Attachment {
659                        key: "fxfs_compressed_data".to_string(),
660                        value: MemBuffer { vmo, size },
661                    }]),
662                    ..Default::default()
663                })
664                .await
665            {
666                error!(e:?; "Failed to file crash report");
667            } else {
668                warn!("Filed crash report for decompression error");
669            }
670        } else {
671            error!("Failed to connect to crash report service");
672        }
673    }
674}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679    use crate::fuchsia::fxblob::testing::{BlobFixture, new_blob_fixture};
680    use crate::fuchsia::pager::PageInRange;
681    use crate::fuchsia::testing::{TestFixture, TestFixtureOptions};
682    use crate::fxblob::testing::open_blob_fixture;
683    use assert_matches::assert_matches;
684    use blob_writer::BlobWriter;
685    use delivery_blob::compression::{ChunkedArchiveOptions, CompressionAlgorithm};
686    use delivery_blob::{CompressionMode, Type1Blob};
687    use fuchsia_async::epoch::Epoch;
688    use fuchsia_component_client::connect_to_protocol_at_dir_svc;
689    use fxfs::object_store::{HandleOptions, ObjectStore};
690    use fxfs_make_blob_image::FxBlobBuilder;
691    use storage_device::DeviceHolder;
692    use storage_device::fake_device::FakeDevice;
693
694    const BLOCK_SIZE: u64 = fuchsia_merkle::BLOCK_SIZE as u64;
695    const CHUNK_SIZE: usize = 32 * 1024;
696
697    #[fuchsia::test(threads = 10)]
698    async fn test_empty_blob() {
699        let fixture = new_blob_fixture().await;
700
701        let data = vec![];
702        let hash = fixture.write_blob(&data, CompressionMode::Never).await;
703        assert_eq!(fixture.read_blob(hash).await, data);
704
705        fixture.close().await;
706    }
707
708    #[fuchsia::test(threads = 10)]
709    async fn test_large_blob() {
710        let fixture = new_blob_fixture().await;
711
712        let data = vec![3; 3_000_000];
713        let hash = fixture.write_blob(&data, CompressionMode::Never).await;
714
715        assert_eq!(fixture.read_blob(hash).await, data);
716
717        fixture.close().await;
718    }
719
720    #[fuchsia::test(threads = 10)]
721    async fn test_get_multiple_mapping_extents() {
722        let fixture = new_blob_fixture().await;
723
724        {
725            let data = vec![0xff; 3_000_000];
726            let hash = fixture.write_blob(&data, CompressionMode::Never).await;
727            let name = format!("{}", hash);
728
729            let handle = fixture.get_blob_handle(&name).await;
730            let mut transaction =
731                handle.new_transaction().await.expect("failed to create transaction");
732            let mut buf = handle.allocate_buffer(8192).await;
733            buf.as_mut_ptr_slice().fill(0xaa);
734            handle
735                .txn_write(&mut transaction, 0, buf.as_ref())
736                .await
737                .expect("txn_write at offset 0 failed");
738            handle
739                .txn_write(&mut transaction, 16384, buf.as_ref())
740                .await
741                .expect("txn_write at offset 16384 failed");
742            transaction.commit().await.expect("failed to commit transaction");
743
744            let blob = fixture.get_blob(hash).await.expect("getting blob failed");
745            let extents = blob.get_mapping_extents().await.expect("get_mapping_extents failed");
746
747            assert!(!extents.merkle.is_empty(), "Expected at least one merkle extent");
748            assert!(
749                extents.data.len() > 1,
750                "Expected multiple data extents, got {}",
751                extents.data.len()
752            );
753
754            // The overwritten extents should each be exactly 8192 bytes.
755            let overwrite_extent = extents
756                .data
757                .iter()
758                .find(|e| e.logical_range().start == 0)
759                .expect("extent at offset 0");
760            assert_eq!(
761                overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
762                8192
763            );
764            let overwrite_extent = extents
765                .data
766                .iter()
767                .find(|e| e.logical_range().start == 16384)
768                .expect("extent at offset 16384");
769            assert_eq!(
770                overwrite_extent.logical_range().end - overwrite_extent.logical_range().start,
771                8192
772            );
773        }
774
775        fixture.close().await;
776    }
777
778    #[fuchsia::test(threads = 10)]
779    async fn test_large_compressed_blob() {
780        let fixture = new_blob_fixture().await;
781
782        let data = vec![3; 3_000_000];
783        let hash = fixture.write_blob(&data, CompressionMode::Always).await;
784
785        assert_eq!(fixture.read_blob(hash).await, data);
786
787        fixture.close().await;
788    }
789
790    #[fuchsia::test(threads = 10)]
791    async fn test_non_page_aligned_blob() {
792        let fixture = new_blob_fixture().await;
793
794        let page_size = page_size().get() as usize;
795        let data = vec![0xffu8; page_size - 1];
796        let hash = fixture.write_blob(&data, CompressionMode::Never).await;
797        assert_eq!(fixture.read_blob(hash).await, data);
798
799        {
800            let vmo = fixture.get_blob_vmo(hash).await;
801            let mut buf = vec![0x11u8; page_size];
802            vmo.read(&mut buf[..], 0).expect("vmo read failed");
803            assert_eq!(data, buf[..data.len()]);
804            // Ensure the tail is zeroed
805            assert_eq!(buf[data.len()], 0);
806        }
807
808        fixture.close().await;
809    }
810
811    #[fuchsia::test(threads = 10)]
812    async fn test_blob_invalid_contents() {
813        let fixture = new_blob_fixture().await;
814
815        let data = vec![0xffu8; (READ_AHEAD_SIZE + BLOCK_SIZE) as usize];
816        let hash = fixture.write_blob(&data, CompressionMode::Never).await;
817        let name = format!("{}", hash);
818
819        {
820            // Overwrite the second read-ahead window.  The first window should successfully verify.
821            let handle = fixture.get_blob_handle(&name).await;
822            let mut transaction =
823                handle.new_transaction().await.expect("failed to create transaction");
824            let mut buf = handle.allocate_buffer(BLOCK_SIZE as usize).await;
825            buf.fill(0);
826            handle
827                .txn_write(&mut transaction, READ_AHEAD_SIZE, buf.as_ref())
828                .await
829                .expect("txn_write failed");
830            transaction.commit().await.expect("failed to commit transaction");
831        }
832
833        {
834            let blob_vmo = fixture.get_blob_vmo(hash).await;
835            let mut buf = vec![0; BLOCK_SIZE as usize];
836            assert_matches!(blob_vmo.read(&mut buf[..], 0), Ok(_));
837            assert_matches!(
838                blob_vmo.read(&mut buf[..], READ_AHEAD_SIZE),
839                Err(zx::Status::IO_DATA_INTEGRITY)
840            );
841        }
842
843        fixture.close().await;
844    }
845
846    #[fuchsia::test(threads = 10)]
847    async fn test_blob_invalid_uncompressed_size() {
848        use fxfs::object_handle::WriteObjectHandle;
849
850        let fixture = new_blob_fixture().await;
851
852        // 1. A single-block blob (uncompressed_size <= BLOCK_SIZE) should have 0 merkle leaves in
853        // metadata. If metadata contains 1 leaf (even if it equals the root hash), opening fails.
854        {
855            let data = vec![0xaa; BLOCK_SIZE as usize];
856            let hash = fixture.write_blob(&data, CompressionMode::Never).await;
857            let handle = fixture.get_blob_handle(&hash.to_string()).await;
858            BlobMetadata { merkle_leaves: vec![hash.into()], format: BlobFormat::Uncompressed }
859                .write_to(&handle)
860                .await
861                .expect("write_to failed");
862            let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
863            assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
864        }
865
866        // 2. A multi-block compressed blob (uncompressed_size > BLOCK_SIZE) with 0 merkle leaves in
867        // metadata must fail to open with IntegrityError.
868        {
869            let data = vec![0xbb; (BLOCK_SIZE * 2) as usize];
870            let hash = fixture.write_blob(&data, CompressionMode::Always).await;
871            let handle = fixture.get_blob_handle(&hash.to_string()).await;
872            let mut metadata = BlobMetadata::read_from(&handle).await.expect("read_from failed");
873            metadata.merkle_leaves.clear();
874            metadata.write_to(&handle).await.expect("write_to failed");
875            let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
876            assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
877        }
878
879        // 3. A multi-block blob whose uncompressed size disagrees with the number of merkle leaves
880        // (even when merkle_leaves validly hashes to the merkle root) must fail with
881        // IntegrityError.
882        {
883            let data = vec![0xcc; (BLOCK_SIZE * 2) as usize];
884            let hash = fixture.write_blob(&data, CompressionMode::Never).await;
885            let handle = fixture.get_blob_handle(&hash.to_string()).await;
886            handle.truncate(BLOCK_SIZE * 3).await.expect("truncate failed");
887            let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
888            assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
889        }
890
891        // 4. A non-empty single-block blob truncated to 0 bytes must fail to open with
892        // IntegrityError.
893        {
894            let data = vec![0xdd; 100];
895            let hash = fixture.write_blob(&data, CompressionMode::Never).await;
896            let handle = fixture.get_blob_handle(&hash.to_string()).await;
897            handle.truncate(0).await.expect("truncate failed");
898            let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
899            assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
900        }
901
902        // 5. An empty blob whose uncompressed size is non-zero must fail to open with
903        // IntegrityError.
904        {
905            let hash = fixture.write_blob(&[], CompressionMode::Never).await;
906            let handle = fixture.get_blob_handle(&hash.to_string()).await;
907            handle.truncate(100).await.expect("truncate failed");
908            let err = fixture.get_blob(hash).await.err().expect("get_blob should fail");
909            assert_matches!(err.downcast_ref::<FxfsError>(), Some(FxfsError::IntegrityError));
910        }
911
912        fixture.close().await;
913    }
914
915    #[fuchsia::test(threads = 10)]
916    async fn test_lz4_blob() {
917        let device = DeviceHolder::new(FakeDevice::new(16384, 512));
918        let blob_data = vec![0xAA; 68 * 1024];
919        let fxblob_builder = FxBlobBuilder::new(device).await.unwrap();
920        let blob = fxblob_builder
921            .generate_blob(blob_data.clone(), Some(CompressionAlgorithm::Lz4))
922            .unwrap();
923        let blob_hash = blob.hash();
924        fxblob_builder.install_blob(&blob).await.unwrap();
925        let device = fxblob_builder.finalize().await.unwrap().0;
926        device.reopen(/*read_only=*/ false);
927        let fixture = open_blob_fixture(device).await;
928
929        assert_eq!(fixture.read_blob(blob_hash).await, blob_data);
930
931        fixture.close().await;
932    }
933
934    #[fuchsia::test(threads = 10)]
935    async fn test_blob_vmos_are_immutable() {
936        let fixture = new_blob_fixture().await;
937
938        let data = vec![0xffu8; 500];
939        let hash = fixture.write_blob(&data, CompressionMode::Never).await;
940        let blob_vmo = fixture.get_blob_vmo(hash).await;
941
942        // The VMO shouldn't be resizable.
943        assert_matches!(blob_vmo.set_size(20), Err(_));
944
945        // The VMO shouldn't be writable.
946        assert_matches!(blob_vmo.write(b"overwrite", 0), Err(_));
947
948        // The VMO's content size shouldn't be modifiable.
949        assert_matches!(blob_vmo.set_stream_size(20), Err(_));
950
951        fixture.close().await;
952    }
953
954    const COMPRESSED_BLOB_CHUNK_SIZE: u64 = 32 * 1024;
955    const MAX_SMALL_OFFSET: u64 = u32::MAX as u64;
956    const ZSTD: CompressionAlgorithm = CompressionAlgorithm::Zstd;
957
958    #[fuchsia::test]
959    fn test_compression_info_offsets_must_start_with_zero() {
960        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[], ZSTD).is_err());
961        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[1], ZSTD).is_err());
962        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0], ZSTD).is_ok());
963    }
964
965    #[fuchsia::test]
966    fn test_compression_info_offsets_must_be_sorted() {
967        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 2], ZSTD).is_ok());
968        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 2, 1], ZSTD).is_err());
969        assert!(CompressionInfo::new(COMPRESSED_BLOB_CHUNK_SIZE, 100, &[0, 1, 1], ZSTD).is_err());
970    }
971
972    #[fuchsia::test]
973    fn test_compression_info_compressed_range_for_uncompressed_range() {
974        fn check_compression_ranges(
975            offsets: &[u64],
976            compressed_size: u64,
977            expected_ranges: &[Range<u64>],
978            chunk_size: u64,
979            read_ahead_size: u64,
980        ) {
981            let compression_info =
982                CompressionInfo::new(chunk_size, compressed_size, offsets, ZSTD).unwrap();
983            for (i, expected_range) in expected_ranges.iter().enumerate() {
984                let i = i as u64;
985                let result = compression_info
986                    .compressed_range_for_uncompressed_range(
987                        &(i * read_ahead_size..(i + 1) * read_ahead_size),
988                    )
989                    .unwrap();
990                assert_eq!(&result, expected_range);
991            }
992        }
993        check_compression_ranges(
994            &[0, 10, 20, 30],
995            30,
996            &[0..10, 10..20, 20..30, 30..30],
997            COMPRESSED_BLOB_CHUNK_SIZE,
998            COMPRESSED_BLOB_CHUNK_SIZE,
999        );
1000        check_compression_ranges(
1001            &[0, 10, 20, 30],
1002            30,
1003            &[0..20, 20..30],
1004            COMPRESSED_BLOB_CHUNK_SIZE,
1005            COMPRESSED_BLOB_CHUNK_SIZE * 2,
1006        );
1007        check_compression_ranges(
1008            &[0, 10, 20, 30],
1009            30,
1010            &[0..30],
1011            COMPRESSED_BLOB_CHUNK_SIZE,
1012            COMPRESSED_BLOB_CHUNK_SIZE * 4,
1013        );
1014        check_compression_ranges(
1015            &[0, 10, 20, 30, MAX_SMALL_OFFSET + 10],
1016            MAX_SMALL_OFFSET + 10,
1017            &[0..MAX_SMALL_OFFSET + 10, MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 10],
1018            COMPRESSED_BLOB_CHUNK_SIZE,
1019            COMPRESSED_BLOB_CHUNK_SIZE * 4,
1020        );
1021        check_compression_ranges(
1022            &[
1023                0,
1024                10,
1025                20,
1026                30,
1027                MAX_SMALL_OFFSET + 10,
1028                MAX_SMALL_OFFSET + 20,
1029                MAX_SMALL_OFFSET + 30,
1030                MAX_SMALL_OFFSET + 40,
1031                MAX_SMALL_OFFSET + 50,
1032            ],
1033            MAX_SMALL_OFFSET + 50,
1034            &[
1035                0..20,
1036                20..MAX_SMALL_OFFSET + 10,
1037                MAX_SMALL_OFFSET + 10..MAX_SMALL_OFFSET + 30,
1038                MAX_SMALL_OFFSET + 30..MAX_SMALL_OFFSET + 50,
1039            ],
1040            COMPRESSED_BLOB_CHUNK_SIZE,
1041            COMPRESSED_BLOB_CHUNK_SIZE * 2,
1042        );
1043    }
1044
1045    #[fuchsia::test]
1046    fn test_compression_info_compressed_range_for_uncompressed_range_errors() {
1047        let compression_info = CompressionInfo::new(
1048            COMPRESSED_BLOB_CHUNK_SIZE,
1049            MAX_SMALL_OFFSET + 50,
1050            &[
1051                0,
1052                10,
1053                20,
1054                30,
1055                MAX_SMALL_OFFSET + 10,
1056                MAX_SMALL_OFFSET + 20,
1057                MAX_SMALL_OFFSET + 30,
1058                MAX_SMALL_OFFSET + 40,
1059                MAX_SMALL_OFFSET + 50,
1060            ],
1061            ZSTD,
1062        )
1063        .unwrap();
1064
1065        // The start of reads must be chunk aligned.
1066        assert!(
1067            compression_info
1068                .compressed_range_for_uncompressed_range(&(1..COMPRESSED_BLOB_CHUNK_SIZE),)
1069                .is_err()
1070        );
1071
1072        // Reading entirely past the last offset isn't allowed.
1073        assert!(
1074            compression_info
1075                .compressed_range_for_uncompressed_range(
1076                    &(COMPRESSED_BLOB_CHUNK_SIZE * 9..COMPRESSED_BLOB_CHUNK_SIZE * 12),
1077                )
1078                .is_err()
1079        );
1080
1081        // Reading a different amount than the read-ahead size isn't allowed for middle offsets.
1082        assert!(
1083            compression_info
1084                .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE + 1),)
1085                .is_err()
1086        );
1087        assert!(
1088            compression_info
1089                .compressed_range_for_uncompressed_range(&(0..COMPRESSED_BLOB_CHUNK_SIZE - 1),)
1090                .is_err()
1091        );
1092        assert!(
1093            compression_info
1094                .compressed_range_for_uncompressed_range(
1095                    &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 + 1),
1096                )
1097                .is_err()
1098        );
1099        assert!(
1100            compression_info
1101                .compressed_range_for_uncompressed_range(
1102                    &(COMPRESSED_BLOB_CHUNK_SIZE..COMPRESSED_BLOB_CHUNK_SIZE * 2 - 1),
1103                )
1104                .is_err()
1105        );
1106
1107        // Reading less than the read-ahead size for the last offset is allowed.
1108        assert!(
1109            compression_info
1110                .compressed_range_for_uncompressed_range(
1111                    &(COMPRESSED_BLOB_CHUNK_SIZE * 8..COMPRESSED_BLOB_CHUNK_SIZE * 8 + 4096),
1112                )
1113                .is_ok()
1114        );
1115    }
1116
1117    #[fuchsia::test]
1118    fn test_read_ahead_size_for_chunk_size() {
1119        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 32 * 1024), 32 * 1024);
1120        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 32 * 1024), 48 * 1024);
1121        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 32 * 1024), 64 * 1024);
1122
1123        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 64 * 1024), 64 * 1024);
1124        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 64 * 1024), 48 * 1024);
1125        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 64 * 1024), 64 * 1024);
1126        assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 64 * 1024), 96 * 1024);
1127
1128        assert_eq!(read_ahead_size_for_chunk_size(32 * 1024, 128 * 1024), 128 * 1024);
1129        assert_eq!(read_ahead_size_for_chunk_size(48 * 1024, 128 * 1024), 96 * 1024);
1130        assert_eq!(read_ahead_size_for_chunk_size(64 * 1024, 128 * 1024), 128 * 1024);
1131        assert_eq!(read_ahead_size_for_chunk_size(96 * 1024, 128 * 1024), 96 * 1024);
1132    }
1133
1134    fn build_compression_info(size: usize) -> (CompressionInfo, Vec<u8>, Vec<u8>) {
1135        let options =
1136            ChunkedArchiveOptions::V3 { compression_algorithm: CompressionAlgorithm::Lz4 };
1137        let mut compressor = options.compressor();
1138        let mut uncompressed_data = Vec::with_capacity(size);
1139        {
1140            let mut run_length = 1;
1141            let mut run_value: u8 = 0;
1142            while uncompressed_data.len() < size {
1143                uncompressed_data
1144                    .resize(std::cmp::min(uncompressed_data.len() + run_length, size), run_value);
1145                run_length = (run_length + 1) % 19 + 1;
1146                run_value = (run_value + 1) % 17;
1147            }
1148        }
1149        let mut compressed_offsets = vec![0];
1150        let mut compressed_data = vec![];
1151        for chunk in uncompressed_data.chunks(CHUNK_SIZE) {
1152            let mut compressed_chunk = compressor.compress(chunk, 0).unwrap();
1153            compressed_data.append(&mut compressed_chunk);
1154            compressed_offsets.push(compressed_data.len() as u64);
1155        }
1156        compressed_offsets.pop();
1157        let compressed_size = compressed_data.len() as u64;
1158        (
1159            CompressionInfo::new(
1160                CHUNK_SIZE as u64,
1161                compressed_size,
1162                &compressed_offsets,
1163                CompressionAlgorithm::Lz4,
1164            )
1165            .unwrap(),
1166            compressed_data,
1167            uncompressed_data,
1168        )
1169    }
1170
1171    #[fuchsia::test]
1172    fn test_compression_info_decompress_single_chunk() {
1173        let (compression_info, compressed_data, uncompressed_data) =
1174            build_compression_info(CHUNK_SIZE);
1175        let mut decompressed_data = vec![0u8; CHUNK_SIZE + 1];
1176
1177        compression_info
1178            .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE], 0)
1179            .expect("failed to decompress");
1180        assert_eq!(uncompressed_data, decompressed_data[0..CHUNK_SIZE]);
1181
1182        // Too small of destination buffer.
1183        compression_info
1184            .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1185            .expect_err("decompression should fail");
1186
1187        // Too large of destination buffer.
1188        compression_info
1189            .decompress(&compressed_data[..], &mut decompressed_data[0..CHUNK_SIZE - 1], 0)
1190            .expect_err("decompression should fail");
1191    }
1192
1193    #[fuchsia::test]
1194    fn test_compression_info_decompress_multiple_chunks() {
1195        fn slice_for_chunks<'a>(
1196            compressed_data: &'a [u8],
1197            compression_info: &CompressionInfo,
1198            chunks: Range<u64>,
1199        ) -> &'a [u8] {
1200            let range = compression_info
1201                .compressed_range_for_uncompressed_range(
1202                    &(chunks.start * CHUNK_SIZE as u64..chunks.end * CHUNK_SIZE as u64),
1203                )
1204                .unwrap();
1205            &compressed_data[range.start as usize..range.end as usize]
1206        }
1207
1208        const BLOB_SIZE: usize = CHUNK_SIZE * 4 + 4096;
1209        let (compression_info, compressed_data, uncompressed_data) =
1210            build_compression_info(BLOB_SIZE);
1211        let mut decompressed_data = vec![0u8; BLOB_SIZE];
1212
1213        // Decompress the entire blob.
1214        compression_info
1215            .decompress(&compressed_data[..], &mut decompressed_data[..], 0)
1216            .expect("failed to decompress");
1217        assert_eq!(uncompressed_data, decompressed_data);
1218
1219        // Decompress just the whole chunks.
1220        compression_info
1221            .decompress(
1222                slice_for_chunks(&compressed_data, &compression_info, 0..4),
1223                &mut decompressed_data[0..CHUNK_SIZE * 4],
1224                0,
1225            )
1226            .expect("failed to decompress");
1227        assert_eq!(&uncompressed_data[0..CHUNK_SIZE], &decompressed_data[0..CHUNK_SIZE]);
1228
1229        // Too small of destination buffer for whole chunks.
1230        compression_info
1231            .decompress(
1232                slice_for_chunks(&compressed_data, &compression_info, 0..4),
1233                &mut decompressed_data[0..CHUNK_SIZE * 4 - 1],
1234                0,
1235            )
1236            .expect_err("decompression should fail");
1237
1238        // Too large of destination buffer for whole chunks.
1239        compression_info
1240            .decompress(
1241                slice_for_chunks(&compressed_data, &compression_info, 0..4),
1242                &mut decompressed_data[0..CHUNK_SIZE * 4 + 1],
1243                0,
1244            )
1245            .expect_err("decompression should fail");
1246
1247        // Decompress just the tail.
1248        let partial_chunk = slice_for_chunks(&compressed_data, &compression_info, 4..5);
1249        compression_info
1250            .decompress(partial_chunk, &mut decompressed_data[0..4096], CHUNK_SIZE as u64 * 4)
1251            .expect("failed to decompress");
1252        assert_eq!(&uncompressed_data[CHUNK_SIZE * 4..], &decompressed_data[0..4096]);
1253
1254        // Too small of destination buffer for the tail.
1255        compression_info
1256            .decompress(partial_chunk, &mut decompressed_data[0..4095], CHUNK_SIZE as u64 * 4)
1257            .expect_err("decompression should fail");
1258
1259        // Too large of destination buffer for the tail.
1260        compression_info
1261            .decompress(partial_chunk, &mut decompressed_data[0..4097], CHUNK_SIZE as u64 * 4)
1262            .expect_err("decompression should fail");
1263    }
1264
1265    #[fuchsia::test(threads = 10)]
1266    async fn test_refault_metric() {
1267        let fixture = new_blob_fixture().await;
1268        {
1269            let volume = fixture.volume().volume().clone();
1270            const FILE_SIZE: u64 = READ_AHEAD_SIZE * 4 - 4096;
1271            let data = vec![0xffu8; FILE_SIZE as usize];
1272            let hash = fixture.write_blob(&data, CompressionMode::Never).await;
1273
1274            let blob = fixture.get_opened_blob(hash).await.unwrap();
1275            // Nothing has been read yet.
1276            assert_eq!(
1277                &blob.pager_state().chunks_supplied.to_vec(4),
1278                &[false, false, false, false]
1279            );
1280
1281            blob.pager_state().vmo.read_to_vec::<u8>(4096, 4096).unwrap();
1282
1283            assert_eq!(&blob.pager_state().chunks_supplied.to_vec(4), &[true, false, false, false]);
1284
1285            blob.pager_state()
1286                .vmo
1287                .read_to_vec::<u8>(READ_AHEAD_SIZE * 2 + 4096, READ_AHEAD_SIZE)
1288                .unwrap();
1289            assert_eq!(&blob.pager_state().chunks_supplied.to_vec(4), &[true, false, true, true]);
1290
1291            // We have loaded pages, but only once each.
1292            let pages_per_chunk = READ_AHEAD_SIZE / page_size();
1293            assert_eq!(volume.page_fault_counter().read_refaults(Ordering::SeqCst), 0);
1294            assert_eq!(
1295                volume.page_fault_counter().read_faults(Ordering::SeqCst),
1296                3 * pages_per_chunk - 1
1297            );
1298
1299            // Re-read some pages.
1300
1301            // We can't evict pages from the VMO to get the kernel to resupply them but we can call
1302            // page_in directly and wait for the counters to change.
1303            blob.clone().page_in(PageInRange::new(
1304                FILE_SIZE - READ_AHEAD_SIZE..FILE_SIZE,
1305                blob.dup(),
1306                Epoch::global().guard(),
1307            ));
1308            Epoch::global().barrier().await;
1309
1310            // Two chunks were resupplied (one full chunk and the last chunk which is one page
1311            // short), and the counter is expressed in pages.
1312            assert_eq!(
1313                volume.page_fault_counter().read_refaults(Ordering::SeqCst),
1314                2 * pages_per_chunk - 1
1315            );
1316            assert_eq!(
1317                volume.page_fault_counter().read_faults(Ordering::SeqCst),
1318                5 * pages_per_chunk - 2
1319            );
1320        }
1321
1322        fixture.close().await;
1323    }
1324
1325    #[fuchsia::test(threads = 10)]
1326    async fn test_driver_blob_paging_does_not_create_vmo() {
1327        let fixture = TestFixture::open(
1328            DeviceHolder::new(FakeDevice::new(16384, 512)),
1329            TestFixtureOptions {
1330                encrypted: false,
1331                as_blob: true,
1332                use_driver_blob_paging: true,
1333                ..Default::default()
1334            },
1335        )
1336        .await;
1337
1338        {
1339            let data = vec![0xabu8; 65_536];
1340            let hash = fixture.write_blob(&data, CompressionMode::Always).await;
1341
1342            let opened_blob = fixture.get_opened_blob(hash).await.expect("get_opened_blob failed");
1343            let old_object_id = opened_blob.object_id();
1344            assert!(!opened_blob.has_fxfs_pager());
1345            assert_eq!(opened_blob.byte_size(), data.len() as u64);
1346            assert!(opened_blob.stored_size().await.expect("stored_size failed") > 0);
1347            let extents =
1348                opened_blob.get_mapping_extents().await.expect("get_mapping_extents failed");
1349            assert!(!extents.data.is_empty());
1350            assert!(!extents.merkle.is_empty());
1351
1352            // Creating a child VMO directly on an FxBlob opened without a pager fails with
1353            // NOT_SUPPORTED.
1354            assert_eq!(
1355                opened_blob.create_child_vmo().expect_err("create_child_vmo succeeded"),
1356                Status::NOT_SUPPORTED
1357            );
1358
1359            // Overwriting the open blob must also succeed without creating a VMO, while keeping
1360            // the old object alive until `opened_blob` is dropped.
1361            let delivery_data = Type1Blob::generate(&data, CompressionMode::Never);
1362            let writer = fixture.create_blob(&hash.into(), true).await.expect("create_blob failed");
1363            let mut blob_writer = BlobWriter::create(writer, delivery_data.len() as u64)
1364                .await
1365                .expect("BlobWriter::create failed");
1366            blob_writer.write(&delivery_data).await.expect("blob_writer.write failed");
1367
1368            fixture.fs().graveyard().flush().await;
1369            assert!(
1370                ObjectStore::open_object(
1371                    fixture.volume().volume(),
1372                    old_object_id,
1373                    HandleOptions::default(),
1374                    None,
1375                )
1376                .await
1377                .is_ok(),
1378                "Overwritten blob object should remain alive while OpenedNode is held"
1379            );
1380
1381            drop(opened_blob);
1382            fixture.fs().graveyard().flush().await;
1383            assert_matches!(
1384                ObjectStore::open_object(
1385                    fixture.volume().volume(),
1386                    old_object_id,
1387                    HandleOptions::default(),
1388                    None,
1389                )
1390                .await
1391                .err()
1392                .expect("Overwritten blob object should be purged after OpenedNode is dropped")
1393                .downcast_ref::<FxfsError>(),
1394                Some(FxfsError::NotFound)
1395            );
1396
1397            let overwritten_blob =
1398                fixture.get_opened_blob(hash).await.expect("get_opened_blob after overwrite");
1399            assert!(!overwritten_blob.has_fxfs_pager());
1400            assert_eq!(overwritten_blob.byte_size(), data.len() as u64);
1401            assert_eq!(
1402                overwritten_blob.stored_size().await.expect("stored_size failed"),
1403                data.len() as u64
1404            );
1405            drop(overwritten_blob);
1406
1407            // Calling BlobReader.GetVmo falls back to Fxfs's internal blob pager.
1408            let blob_reader =
1409                connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobReaderMarker>(
1410                    fixture.volume_out_dir(),
1411                )
1412                .expect("connect_to_protocol_at_dir_svc failed");
1413            let vmo = blob_reader
1414                .get_vmo(&hash.into())
1415                .await
1416                .expect("get_vmo FIDL failed")
1417                .map_err(Status::err_from_raw)
1418                .expect("get_vmo failed");
1419            let mut read_buf = vec![0u8; data.len()];
1420            vmo.read(&mut read_buf, 0).expect("vmo read failed");
1421            assert_eq!(read_buf, data);
1422        }
1423
1424        fixture.close().await;
1425    }
1426}