Skip to main content

block_server/
verifier.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use delivery_blob::compression::{ChunkedArchiveError, DataBuffer};
6use fuchsia_sync::Mutex;
7use mapping::{
8    DeliveryCommand, DeliveryHandler, PENDING_DELIVERY_COMMANDS_CAPACITY, PageRequest,
9    RawDeliveryCommand,
10};
11use std::ops::Range;
12use std::sync::Arc;
13use storage_ptr_slice::MutPtrByteSlice;
14use vmo_fifo::SyncSender;
15
16/// Manages data verification and page supply via the delivery queue VMO.
17pub struct Verifier {
18    sender: Mutex<Option<SyncSender<RawDeliveryCommand>>>,
19}
20
21impl Verifier {
22    pub fn new(delivery_queue: zx::Vmo) -> Self {
23        let sender = if delivery_queue.get_size().unwrap_or(0) > 0 {
24            // Payloads in the delivery queue are transferred to the kernel pager via
25            // `zx_pager_supply_pages`, which requires the source offset to be page-aligned. Set
26            // the alignment of payload offsets to be page aligned.
27            SyncSender::<RawDeliveryCommand>::new(
28                delivery_queue,
29                zx::system_get_page_size() as usize,
30                PENDING_DELIVERY_COMMANDS_CAPACITY,
31            )
32            .ok()
33        } else {
34            None
35        };
36        Self { sender: Mutex::new(sender) }
37    }
38}
39
40impl DeliveryHandler for Verifier {
41    type Request = Buffer;
42
43    /// Returns a [`PageRequest`] implementation for delivering page-in data.
44    fn get_page_request(self: &Arc<Self>, key: u64, original_range: Range<u64>) -> Buffer {
45        Buffer {
46            verifier: Arc::clone(self),
47            key,
48            read_range: original_range,
49            read_alignment: 0,
50            committed_len: 0,
51            delivered_len: 0,
52            data: Vec::new(),
53            child_target: None,
54        }
55    }
56
57    /// Returns a [`Buffer`] configured to deliver parent blob chunks for a child file extent via
58    /// [`DeliveryCommand::ChildData`].
59    fn get_child_page_request(
60        self: &Arc<Self>,
61        child_key: u64,
62        child_target_offset: u64,
63        parent_byte_range: Range<u64>,
64    ) -> Buffer {
65        Buffer {
66            verifier: Arc::clone(self),
67            key: child_key,
68            read_range: parent_byte_range.clone(),
69            read_alignment: 0,
70            committed_len: 0,
71            delivered_len: 0,
72            data: Vec::new(),
73            child_target: Some(ChildTarget { child_target_offset, parent_byte_range }),
74        }
75    }
76
77    /// Pushes a header-only [`DeliveryCommand::ChildInlineTail`] command onto the delivery queue
78    /// so the verifier can supply the child tail page from the verified parent VMO.
79    fn deliver_child_inline_tail(
80        self: &Arc<Self>,
81        parent_byte_offset: u64,
82        child_key: u64,
83        child_target_offset: u64,
84        tail_length: u32,
85    ) {
86        let mut sender = self.sender.lock();
87        if let Some(sender) = sender.as_mut() {
88            let cmd = DeliveryCommand::ChildInlineTail {
89                parent_byte_offset,
90                child_key,
91                child_target_offset,
92                tail_length,
93            };
94            let _ = sender.push(cmd.into());
95        }
96    }
97
98    /// Pushes a [`DeliveryCommand::ChildData`] command with `length == 0` onto the delivery queue
99    /// to supply a zero-filled sparse range to `child_key`.
100    fn deliver_child_sparse(
101        &self,
102        child_key: u64,
103        child_target_offset: u64,
104        child_length: u64,
105    ) -> Result<(), anyhow::Error> {
106        let mut sender = self.sender.lock();
107        if let Some(sender) = sender.as_mut() {
108            let cmd = DeliveryCommand::ChildData {
109                parent_offset: 0,
110                offset: 0,
111                length: 0,
112                child_key,
113                child_target_offset,
114                in_chunk_offset: 0,
115                child_length: child_length as u32,
116            };
117            sender.push(cmd.into())?;
118        }
119        Ok(())
120    }
121
122    /// Registers a blob's Merkle tree leaf hashes with the verifier via the delivery queue.
123    fn register_blob(
124        &self,
125        key: u64,
126        merkle_leaves: &[[u8; 32]],
127        read_alignment: usize,
128    ) -> Result<(), anyhow::Error> {
129        let mut sender = self.sender.lock();
130        if let Some(sender) = sender.as_mut() {
131            let leaf_bytes: &[u8] = merkle_leaves.as_flattened();
132            let mut payload = sender.reserve_payload(leaf_bytes.len())?;
133            payload.data().copy_from_slice(leaf_bytes);
134            let cmd = DeliveryCommand::RegisterBlob {
135                key,
136                offset: payload.offset(),
137                length: leaf_bytes.len() as u32,
138                read_alignment: read_alignment as u32,
139            };
140            payload.commit(cmd.into())?;
141        }
142        Ok(())
143    }
144}
145
146/// Target metadata for a [`Buffer`] servicing a page request on a child file mapped over a
147/// parent blob.
148struct ChildTarget {
149    /// Starting page-aligned logical byte offset in the child VMO for this request.
150    child_target_offset: u64,
151    /// Byte range in the parent blob corresponding to this child extent slice.
152    parent_byte_range: Range<u64>,
153}
154
155/// Implementation of [`PageRequest`] for verifier deliveries that forwards
156/// unverified chunks via the delivery queue.
157pub struct Buffer {
158    verifier: Arc<Verifier>,
159    key: u64,
160    read_range: Range<u64>,
161    // Alignment boundary (matching the blob's `read_alignment`) at which data is flushed to the
162    // verifier queue.
163    read_alignment: usize,
164    // Bytes committed into `self.data` so far via calls to [`DataBuffer::commit`].
165    committed_len: usize,
166    // Bytes delivered to the verifier queue so far.
167    delivered_len: usize,
168    data: Vec<u8>,
169    child_target: Option<ChildTarget>,
170}
171
172impl Buffer {
173    fn deliver_chunk(&mut self, size: usize) -> Result<(), ChunkedArchiveError> {
174        // TODO(https://fxbug.dev/530494057): Optimize buffer management / payload reservations.
175        let chunk_data = &self.data[self.delivered_len..self.delivered_len + size];
176        let offset = self.read_range.start + self.delivered_len as u64;
177
178        let mut sender = self.verifier.sender.lock();
179        if let Some(sender) = sender.as_mut() {
180            let page_size = zx::system_get_page_size() as usize;
181            let aligned_size = size.next_multiple_of(page_size);
182            let mut payload = sender.reserve_payload(aligned_size).map_err(|e| {
183                log::error!(e:?; "Verifier::deliver_chunk: reserve_payload failed");
184                ChunkedArchiveError::IntegrityError
185            })?;
186
187            let payload_data = payload.data();
188            payload_data.subslice_mut(0..size).copy_from_slice(chunk_data);
189            payload_data.subslice_mut(size..aligned_size).fill(0);
190            let cmd = if let Some(target) = &self.child_target {
191                let chunk_end = offset + aligned_size as u64;
192                let slice_start = std::cmp::max(offset, target.parent_byte_range.start);
193                let slice_end = std::cmp::min(chunk_end, target.parent_byte_range.end);
194                let in_chunk_offset = (slice_start - offset) as u32;
195                let child_target_offset =
196                    target.child_target_offset + (slice_start - target.parent_byte_range.start);
197                let child_length = (slice_end.saturating_sub(slice_start)) as u32;
198                DeliveryCommand::ChildData {
199                    parent_offset: offset,
200                    offset: payload.offset(),
201                    length: aligned_size as u32,
202                    child_key: self.key,
203                    child_target_offset,
204                    in_chunk_offset,
205                    child_length,
206                }
207            } else {
208                DeliveryCommand::Data {
209                    key: self.key,
210                    target_offset: offset,
211                    length: aligned_size as u32,
212                    offset: payload.offset(),
213                }
214            };
215            payload.commit(cmd.into()).map_err(|_| ChunkedArchiveError::IntegrityError)?;
216        }
217
218        self.delivered_len += size;
219        Ok(())
220    }
221}
222
223impl DataBuffer for Buffer {
224    fn range(&self) -> Range<u64> {
225        self.read_range.clone()
226    }
227
228    /// Returns a mutable slice into the mapped transfer buffer.
229    ///
230    /// # Panics
231    ///
232    /// Panics if `prepare()` has not been called prior to accessing this method.
233    fn mut_ptr_slice(&mut self) -> MutPtrByteSlice<'_> {
234        assert!(!self.data.is_empty(), "prepare must be called before accessing mut_ptr_slice");
235        MutPtrByteSlice::from(&mut self.data[self.committed_len..])
236    }
237
238    fn commit(&mut self, size: usize) -> Result<(), ChunkedArchiveError> {
239        const VERIFICATION_FLUSH_THRESHOLD: usize = 96 * 1024;
240
241        self.committed_len += size;
242
243        if self.committed_len < self.data.len() {
244            // Once at least `VERIFICATION_FLUSH_THRESHOLD` (96 KiB, rounded to a multiple of
245            // `read_alignment`) has been committed, deliver complete `read_alignment` chunks so
246            // verification overlaps with decompressing the remainder of the readahead window.
247            let flush_threshold =
248                (VERIFICATION_FLUSH_THRESHOLD / self.read_alignment).max(1) * self.read_alignment;
249            if self.committed_len - self.delivered_len >= flush_threshold {
250                let to_deliver = ((self.committed_len - self.delivered_len) / self.read_alignment)
251                    * self.read_alignment;
252                self.deliver_chunk(to_deliver)?;
253            }
254        } else if self.delivered_len < self.committed_len {
255            // Deliver any remaining bytes once the entire prepared range has been committed.
256            self.deliver_chunk(self.committed_len - self.delivered_len)?;
257        }
258
259        Ok(())
260    }
261}
262
263impl PageRequest for Buffer {
264    fn prepare(
265        &mut self,
266        read_range: Range<u64>,
267        read_alignment: usize,
268    ) -> Result<(), ChunkedArchiveError> {
269        assert!(self.data.is_empty(), "prepare must only be called once");
270        assert!(read_alignment > 0, "read_alignment must be non-zero");
271        self.read_range = read_range;
272        self.read_alignment = read_alignment;
273        let len = (self.read_range.end - self.read_range.start) as usize;
274        self.data = vec![0u8; len];
275        Ok(())
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use mapping::{DELIVERY_DATA_COMMAND, TARGET_READ_AHEAD_SIZE};
283    use test_case::test_case;
284
285    #[fuchsia::test]
286    fn test_verifier_buffer_incremental_commit() {
287        let delivery_queue = zx::Vmo::create(65536).unwrap();
288        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
289            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
290            PENDING_DELIVERY_COMMANDS_CAPACITY,
291        )
292        .unwrap();
293
294        let verifier = Arc::new(Verifier::new(delivery_queue));
295        let key = 42u64;
296
297        let mut request = verifier.get_page_request(key, 0..8192);
298        request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
299
300        // Fill first page (4096 bytes) with 0xAA
301        let page1_data = vec![0xAAu8; 4096];
302        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page1_data);
303        request.commit(4096).expect("commit page 1");
304
305        // Verify mut_ptr_slice advanced to second page
306        assert_eq!(request.mut_ptr_slice().len(), 4096);
307        // Not all data has been committed yet, so no command should be delivered yet.
308        assert!(receiver.is_empty());
309
310        // Fill second page (4096 bytes) with 0xBB
311        let page2_data = vec![0xBBu8; 4096];
312        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page2_data);
313        request.commit(4096).expect("commit page 2");
314
315        // Verify remaining space is 0
316        assert_eq!(request.mut_ptr_slice().len(), 0);
317
318        // Check single consolidated message delivered on queue
319        let msg = receiver.peek().expect("msg");
320        assert_eq!(msg.opcode, DELIVERY_DATA_COMMAND);
321        assert_eq!(msg.key, key);
322        assert_eq!(msg.length, 8192);
323        assert_eq!(msg.target_offset, 0);
324        let mut buf = vec![0u8; 8192];
325        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
326        assert_eq!(&buf[..4096], &page1_data[..]);
327        assert_eq!(&buf[4096..], &page2_data[..]);
328        msg.pop().expect("pop msg");
329    }
330
331    #[fuchsia::test]
332    fn test_verifier_buffer_page_aligned_commits() {
333        let delivery_queue = zx::Vmo::create(65536).unwrap();
334        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
335            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
336            PENDING_DELIVERY_COMMANDS_CAPACITY,
337        )
338        .unwrap();
339
340        let verifier = Arc::new(Verifier::new(delivery_queue));
341        let key = 43u64;
342
343        let mut request = verifier.get_page_request(key, 0..5000);
344        request.prepare(0..5000, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
345
346        let data = vec![0xCCu8; 5000];
347
348        // Commit first 4096 bytes
349        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&data[..4096]);
350        request.commit(4096).expect("commit 4096 bytes");
351        assert!(receiver.is_empty());
352
353        // Commit remaining 904 bytes (completes the prepared range)
354        request.mut_ptr_slice().subslice_mut(0..904).copy_from_slice(&data[4096..5000]);
355        request.commit(904).expect("commit 904 bytes");
356
357        // Check single message delivered, page-aligned to 8192
358        let msg = receiver.peek().expect("msg");
359        assert_eq!(msg.target_offset, 0);
360        assert_eq!(msg.length, 8192);
361        let mut buf = vec![0u8; 8192];
362        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
363        assert_eq!(&buf[..5000], &data[..]);
364        assert_eq!(&buf[5000..], &[0u8; 3192]);
365        msg.pop().expect("pop msg");
366    }
367
368    #[test_case(32 * 1024, 128 * 1024; "zstd_32k_read_128k")]
369    #[test_case(48 * 1024, 96 * 1024; "zstd_48k_read_96k")]
370    #[test_case(152 * 1024, 152 * 1024; "zstd_152k_read_152k")]
371    #[fuchsia::test]
372    fn test_verifier_buffer_delivery_data_size_chunking(
373        zstd_chunk_size: usize,
374        read_alignment: usize,
375    ) {
376        let total_size = read_alignment * 2;
377        let delivery_queue = zx::Vmo::create(total_size as u64 + 65536).unwrap();
378        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
379            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
380            PENDING_DELIVERY_COMMANDS_CAPACITY,
381        )
382        .unwrap();
383
384        let verifier = Arc::new(Verifier::new(delivery_queue));
385        let key = 44u64;
386
387        let mut request = verifier.get_page_request(key, 0..total_size as u64);
388        request.prepare(0..total_size as u64, read_alignment).expect("prepare");
389
390        let chunk = vec![0x55u8; zstd_chunk_size];
391        let chunks_per_read = read_alignment / zstd_chunk_size;
392
393        // Push `chunks_per_read - 1` chunks - less than `read_alignment`
394        for _ in 0..(chunks_per_read - 1) {
395            request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
396            request.commit(zstd_chunk_size).expect("commit");
397        }
398        assert!(receiver.is_empty());
399
400        // Push the final chunk of the first `read_alignment` window
401        request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
402        request.commit(zstd_chunk_size).expect("commit");
403
404        // 1st `read_alignment` message delivered
405        let msg1 = receiver.peek().expect("msg1");
406        assert_eq!(msg1.target_offset, 0);
407        assert_eq!(msg1.length, read_alignment as u32);
408        msg1.pop().expect("pop msg1");
409
410        // Push remaining `chunks_per_read` chunks (second `read_alignment` window)
411        for _ in 0..chunks_per_read {
412            request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
413            request.commit(zstd_chunk_size).expect("commit");
414        }
415
416        // 2nd `read_alignment` message delivered
417        let msg2 = receiver.peek().expect("msg2");
418        assert_eq!(msg2.target_offset, read_alignment as u64);
419        assert_eq!(msg2.length, read_alignment as u32);
420        msg2.pop().expect("pop msg2");
421    }
422
423    #[fuchsia::test]
424    #[should_panic(expected = "prepare must only be called once")]
425    fn test_verifier_buffer_prepare_multiple_times_panics() {
426        let delivery_queue = zx::Vmo::create(4096).unwrap();
427        let verifier = Arc::new(Verifier::new(delivery_queue));
428
429        let key = 46u64;
430        let mut request = verifier.get_page_request(key, 0..8192);
431        request.prepare(0..4096, TARGET_READ_AHEAD_SIZE as usize).expect("first prepare");
432        let _ = request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize);
433    }
434
435    #[fuchsia::test]
436    fn test_verifier_buffer_supplies_pages_via_delivery_receiver() {
437        let pager = Arc::new(zx::Pager::create(zx::PagerOptions::empty()).unwrap());
438        let port = zx::Port::create();
439        let key = 44u64;
440        let paged_vmo = pager.create_vmo(zx::VmoOptions::empty(), &port, key, 8192).unwrap();
441
442        let delivery_queue = zx::Vmo::create(65536).unwrap();
443        let vmo_provider = Arc::new(blob_pager_and_verifier::TestVmoProvider::new(
444            pager.clone(),
445            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
446        ));
447        vmo_provider
448            .register_vmo(key, paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
449        let receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
450            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
451            PENDING_DELIVERY_COMMANDS_CAPACITY,
452        )
453        .unwrap();
454        let _processor = blob_pager_and_verifier::DeliveryQueueProcessor::spawn(
455            receiver,
456            vmo_provider,
457            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
458        )
459        .unwrap();
460
461        let verifier = Arc::new(Verifier::new(delivery_queue));
462        let mut request = verifier.get_page_request(key, 0..8192);
463        request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
464
465        let page1_data = vec![0xAAu8; 4096];
466        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page1_data);
467        request.commit(4096).expect("commit page 1");
468
469        let page2_data = vec![0xBBu8; 4096];
470        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page2_data);
471        request.commit(4096).expect("commit page 2");
472
473        let mut read_buf = vec![0u8; 8192];
474        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo");
475        assert_eq!(&read_buf[..4096], &page1_data[..]);
476        assert_eq!(&read_buf[4096..8192], &page2_data[..]);
477    }
478
479    #[fuchsia::test]
480    fn test_verifier_register_blob() {
481        let delivery_queue = zx::Vmo::create(65536).unwrap();
482        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
483            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
484            PENDING_DELIVERY_COMMANDS_CAPACITY,
485        )
486        .unwrap();
487
488        let verifier = Arc::new(Verifier::new(delivery_queue));
489        let key = 42u64;
490        let leaves = [[0xABu8; 32], [0xCDu8; 32]];
491
492        verifier.register_blob(key, &leaves, 96 * 1024).expect("register_blob failed");
493
494        let msg = receiver.peek().expect("peek msg");
495        assert_eq!(msg.opcode, mapping::DELIVERY_REGISTER_BLOB_COMMAND);
496        assert_eq!(msg.key, key);
497        assert_eq!(msg.length, 64);
498        assert_eq!(msg.read_alignment, 96 * 1024);
499        let mut buf = vec![0u8; 64];
500        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
501        assert_eq!(&buf[..32], &[0xABu8; 32]);
502        assert_eq!(&buf[32..], &[0xCDu8; 32]);
503        msg.pop().expect("pop msg failed");
504    }
505
506    #[fuchsia::test]
507    fn test_verifier_buffer_96k_overlap() {
508        let total_size = 128 * 1024;
509        let chunk_size = 32 * 1024;
510        let delivery_queue = zx::Vmo::create(total_size as u64 * 2 + 65536).unwrap();
511        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
512            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
513            PENDING_DELIVERY_COMMANDS_CAPACITY,
514        )
515        .unwrap();
516
517        let verifier = Arc::new(Verifier::new(delivery_queue));
518        let key = 45u64;
519
520        let mut request = verifier.get_page_request(key, 0..total_size as u64);
521        request.prepare(0..total_size as u64, chunk_size).expect("prepare");
522
523        let chunk = vec![0x55u8; chunk_size];
524
525        // First two 32 KiB chunks (64 KiB total) should not flush yet.
526        for _ in 0..2 {
527            request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
528            request.commit(chunk_size).expect("commit");
529        }
530        assert!(receiver.is_empty());
531
532        // Third 32 KiB chunk reaches 96 KiB and flushes 96 KiB so verification overlaps with
533        // decompressing the final 32 KiB.
534        request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
535        request.commit(chunk_size).expect("commit");
536
537        let msg1 = receiver.peek().expect("msg1");
538        assert_eq!(msg1.target_offset, 0);
539        assert_eq!(msg1.length, 96 * 1024);
540        msg1.pop().expect("pop msg1");
541
542        // Fourth 32 KiB chunk completes the 128 KiB window and flushes the remaining 32 KiB.
543        request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
544        request.commit(chunk_size).expect("commit");
545
546        let msg2 = receiver.peek().expect("msg2");
547        assert_eq!(msg2.target_offset, 96 * 1024);
548        assert_eq!(msg2.length, 32 * 1024);
549        msg2.pop().expect("pop msg2");
550    }
551}