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        }
54    }
55
56    /// Registers a blob's Merkle tree leaf hashes with the verifier via the delivery queue.
57    fn register_blob(
58        &self,
59        key: u64,
60        merkle_leaves: &[[u8; 32]],
61        read_alignment: usize,
62    ) -> Result<(), anyhow::Error> {
63        let mut sender_guard = self.sender.lock();
64        if let Some(sender) = sender_guard.as_mut() {
65            let leaf_bytes: &[u8] = merkle_leaves.as_flattened();
66            let mut payload = sender.reserve_payload(leaf_bytes.len())?;
67            payload.data().copy_from_slice(leaf_bytes);
68            let cmd = DeliveryCommand::RegisterBlob {
69                key,
70                offset: payload.offset(),
71                length: leaf_bytes.len() as u32,
72                read_alignment: read_alignment as u32,
73            };
74            payload.commit(cmd.into())?;
75        }
76        Ok(())
77    }
78}
79
80/// Implementation of [`PageRequest`] for verifier deliveries that forwards
81/// unverified chunks via the delivery queue.
82pub struct Buffer {
83    verifier: Arc<Verifier>,
84    key: u64,
85    read_range: Range<u64>,
86    // Alignment boundary (matching the blob's `read_alignment`) at which data is flushed to the
87    // verifier queue.
88    read_alignment: usize,
89    // Bytes committed into `self.data` so far via calls to [`DataBuffer::commit`].
90    committed_len: usize,
91    // Bytes delivered to the verifier queue so far.
92    delivered_len: usize,
93    data: Vec<u8>,
94}
95
96impl Buffer {
97    fn deliver_chunk(&mut self, size: usize) -> Result<(), ChunkedArchiveError> {
98        // TODO(https://fxbug.dev/530494057): Optimize buffer management / payload reservations.
99        let chunk_data = &self.data[self.delivered_len..self.delivered_len + size];
100        let offset = self.read_range.start + self.delivered_len as u64;
101
102        let mut sender_guard = self.verifier.sender.lock();
103        if let Some(sender) = sender_guard.as_mut() {
104            let page_size = zx::system_get_page_size() as usize;
105            let aligned_size = size.next_multiple_of(page_size);
106            let mut payload = sender.reserve_payload(aligned_size).map_err(|e| {
107                log::error!(e:?; "Verifier::deliver_chunk: reserve_payload failed");
108                ChunkedArchiveError::IntegrityError
109            })?;
110
111            let payload_data = payload.data();
112            payload_data.subslice_mut(0..size).copy_from_slice(chunk_data);
113            payload_data.subslice_mut(size..aligned_size).fill(0);
114            let cmd = DeliveryCommand::Data {
115                key: self.key,
116                target_offset: offset,
117                length: aligned_size as u32,
118                offset: payload.offset(),
119            };
120            payload.commit(cmd.into()).map_err(|_| ChunkedArchiveError::IntegrityError)?;
121        }
122
123        self.delivered_len += size;
124        Ok(())
125    }
126}
127
128impl DataBuffer for Buffer {
129    fn range(&self) -> Range<u64> {
130        self.read_range.clone()
131    }
132
133    /// Returns a mutable slice into the mapped transfer buffer.
134    ///
135    /// # Panics
136    ///
137    /// Panics if `prepare()` has not been called prior to accessing this method.
138    fn mut_ptr_slice(&mut self) -> MutPtrByteSlice<'_> {
139        assert!(!self.data.is_empty(), "prepare must be called before accessing mut_ptr_slice");
140        MutPtrByteSlice::from(&mut self.data[self.committed_len..])
141    }
142
143    fn commit(&mut self, size: usize) -> Result<(), ChunkedArchiveError> {
144        const VERIFICATION_FLUSH_THRESHOLD: usize = 96 * 1024;
145
146        self.committed_len += size;
147
148        if self.committed_len < self.data.len() {
149            // Once at least `VERIFICATION_FLUSH_THRESHOLD` (96 KiB, rounded to a multiple of
150            // `read_alignment`) has been committed, deliver complete `read_alignment` chunks so
151            // verification overlaps with decompressing the remainder of the readahead window.
152            let flush_threshold =
153                (VERIFICATION_FLUSH_THRESHOLD / self.read_alignment).max(1) * self.read_alignment;
154            if self.committed_len - self.delivered_len >= flush_threshold {
155                let to_deliver = ((self.committed_len - self.delivered_len) / self.read_alignment)
156                    * self.read_alignment;
157                self.deliver_chunk(to_deliver)?;
158            }
159        } else if self.delivered_len < self.committed_len {
160            // Deliver any remaining bytes once the entire prepared range has been committed.
161            self.deliver_chunk(self.committed_len - self.delivered_len)?;
162        }
163
164        Ok(())
165    }
166}
167
168impl PageRequest for Buffer {
169    fn prepare(
170        &mut self,
171        read_range: Range<u64>,
172        read_alignment: usize,
173    ) -> Result<(), ChunkedArchiveError> {
174        assert!(self.data.is_empty(), "prepare must only be called once");
175        assert!(read_alignment > 0, "read_alignment must be non-zero");
176        self.read_range = read_range;
177        self.read_alignment = read_alignment;
178        let len = (self.read_range.end - self.read_range.start) as usize;
179        self.data = vec![0u8; len];
180        Ok(())
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use mapping::{DELIVERY_DATA_COMMAND, TARGET_READ_AHEAD_SIZE};
188    use test_case::test_case;
189
190    #[fuchsia::test]
191    fn test_verifier_buffer_incremental_commit() {
192        let delivery_queue = zx::Vmo::create(65536).unwrap();
193        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
194            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
195            PENDING_DELIVERY_COMMANDS_CAPACITY,
196        )
197        .unwrap();
198
199        let verifier = Arc::new(Verifier::new(delivery_queue));
200        let key = 42u64;
201
202        let mut request = verifier.get_page_request(key, 0..8192);
203        request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
204
205        // Fill first page (4096 bytes) with 0xAA
206        let page1_data = vec![0xAAu8; 4096];
207        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page1_data);
208        request.commit(4096).expect("commit page 1");
209
210        // Verify mut_ptr_slice advanced to second page
211        assert_eq!(request.mut_ptr_slice().len(), 4096);
212        // Not all data has been committed yet, so no command should be delivered yet.
213        assert!(receiver.is_empty());
214
215        // Fill second page (4096 bytes) with 0xBB
216        let page2_data = vec![0xBBu8; 4096];
217        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page2_data);
218        request.commit(4096).expect("commit page 2");
219
220        // Verify remaining space is 0
221        assert_eq!(request.mut_ptr_slice().len(), 0);
222
223        // Check single consolidated message delivered on queue
224        let msg = receiver.peek().expect("msg");
225        assert_eq!(msg.opcode, DELIVERY_DATA_COMMAND);
226        assert_eq!(msg.key, key);
227        assert_eq!(msg.length, 8192);
228        assert_eq!(msg.target_offset, 0);
229        let mut buf = vec![0u8; 8192];
230        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
231        assert_eq!(&buf[..4096], &page1_data[..]);
232        assert_eq!(&buf[4096..], &page2_data[..]);
233        msg.pop().expect("pop msg");
234    }
235
236    #[fuchsia::test]
237    fn test_verifier_buffer_page_aligned_commits() {
238        let delivery_queue = zx::Vmo::create(65536).unwrap();
239        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
240            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
241            PENDING_DELIVERY_COMMANDS_CAPACITY,
242        )
243        .unwrap();
244
245        let verifier = Arc::new(Verifier::new(delivery_queue));
246        let key = 43u64;
247
248        let mut request = verifier.get_page_request(key, 0..5000);
249        request.prepare(0..5000, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
250
251        let data = vec![0xCCu8; 5000];
252
253        // Commit first 4096 bytes
254        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&data[..4096]);
255        request.commit(4096).expect("commit 4096 bytes");
256        assert!(receiver.is_empty());
257
258        // Commit remaining 904 bytes (completes the prepared range)
259        request.mut_ptr_slice().subslice_mut(0..904).copy_from_slice(&data[4096..5000]);
260        request.commit(904).expect("commit 904 bytes");
261
262        // Check single message delivered, page-aligned to 8192
263        let msg = receiver.peek().expect("msg");
264        assert_eq!(msg.target_offset, 0);
265        assert_eq!(msg.length, 8192);
266        let mut buf = vec![0u8; 8192];
267        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
268        assert_eq!(&buf[..5000], &data[..]);
269        assert_eq!(&buf[5000..], &[0u8; 3192]);
270        msg.pop().expect("pop msg");
271    }
272
273    #[test_case(32 * 1024, 128 * 1024; "zstd_32k_read_128k")]
274    #[test_case(48 * 1024, 96 * 1024; "zstd_48k_read_96k")]
275    #[test_case(152 * 1024, 152 * 1024; "zstd_152k_read_152k")]
276    #[fuchsia::test]
277    fn test_verifier_buffer_delivery_data_size_chunking(
278        zstd_chunk_size: usize,
279        read_alignment: usize,
280    ) {
281        let total_size = read_alignment * 2;
282        let delivery_queue = zx::Vmo::create(total_size as u64 + 65536).unwrap();
283        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
284            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
285            PENDING_DELIVERY_COMMANDS_CAPACITY,
286        )
287        .unwrap();
288
289        let verifier = Arc::new(Verifier::new(delivery_queue));
290        let key = 44u64;
291
292        let mut request = verifier.get_page_request(key, 0..total_size as u64);
293        request.prepare(0..total_size as u64, read_alignment).expect("prepare");
294
295        let chunk = vec![0x55u8; zstd_chunk_size];
296        let chunks_per_read = read_alignment / zstd_chunk_size;
297
298        // Push `chunks_per_read - 1` chunks - less than `read_alignment`
299        for _ in 0..(chunks_per_read - 1) {
300            request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
301            request.commit(zstd_chunk_size).expect("commit");
302        }
303        assert!(receiver.is_empty());
304
305        // Push the final chunk of the first `read_alignment` window
306        request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
307        request.commit(zstd_chunk_size).expect("commit");
308
309        // 1st `read_alignment` message delivered
310        let msg1 = receiver.peek().expect("msg1");
311        assert_eq!(msg1.target_offset, 0);
312        assert_eq!(msg1.length, read_alignment as u32);
313        msg1.pop().expect("pop msg1");
314
315        // Push remaining `chunks_per_read` chunks (second `read_alignment` window)
316        for _ in 0..chunks_per_read {
317            request.mut_ptr_slice().subslice_mut(0..zstd_chunk_size).copy_from_slice(&chunk);
318            request.commit(zstd_chunk_size).expect("commit");
319        }
320
321        // 2nd `read_alignment` message delivered
322        let msg2 = receiver.peek().expect("msg2");
323        assert_eq!(msg2.target_offset, read_alignment as u64);
324        assert_eq!(msg2.length, read_alignment as u32);
325        msg2.pop().expect("pop msg2");
326    }
327
328    #[fuchsia::test]
329    #[should_panic(expected = "prepare must only be called once")]
330    fn test_verifier_buffer_prepare_multiple_times_panics() {
331        let delivery_queue = zx::Vmo::create(4096).unwrap();
332        let verifier = Arc::new(Verifier::new(delivery_queue));
333
334        let key = 46u64;
335        let mut request = verifier.get_page_request(key, 0..8192);
336        request.prepare(0..4096, TARGET_READ_AHEAD_SIZE as usize).expect("first prepare");
337        let _ = request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize);
338    }
339
340    #[fuchsia::test]
341    fn test_verifier_buffer_supplies_pages_via_delivery_receiver() {
342        let pager = Arc::new(zx::Pager::create(zx::PagerOptions::empty()).unwrap());
343        let port = zx::Port::create();
344        let key = 44u64;
345        let paged_vmo = pager.create_vmo(zx::VmoOptions::empty(), &port, key, 8192).unwrap();
346
347        let delivery_queue = zx::Vmo::create(65536).unwrap();
348        let vmo_provider = Arc::new(blob_pager_and_verifier::TestVmoProvider::new(
349            pager.clone(),
350            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
351        ));
352        vmo_provider
353            .register_vmo(key, paged_vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap());
354        let receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
355            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
356            PENDING_DELIVERY_COMMANDS_CAPACITY,
357        )
358        .unwrap();
359        let _processor = blob_pager_and_verifier::DeliveryQueueProcessor::spawn(
360            receiver,
361            vmo_provider,
362            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
363        )
364        .unwrap();
365
366        let verifier = Arc::new(Verifier::new(delivery_queue));
367        let mut request = verifier.get_page_request(key, 0..8192);
368        request.prepare(0..8192, TARGET_READ_AHEAD_SIZE as usize).expect("prepare");
369
370        let page1_data = vec![0xAAu8; 4096];
371        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page1_data);
372        request.commit(4096).expect("commit page 1");
373
374        let page2_data = vec![0xBBu8; 4096];
375        request.mut_ptr_slice().subslice_mut(0..4096).copy_from_slice(&page2_data);
376        request.commit(4096).expect("commit page 2");
377
378        let mut read_buf = vec![0u8; 8192];
379        paged_vmo.read(&mut read_buf, 0).expect("read paged_vmo");
380        assert_eq!(&read_buf[..4096], &page1_data[..]);
381        assert_eq!(&read_buf[4096..8192], &page2_data[..]);
382    }
383
384    #[fuchsia::test]
385    fn test_verifier_register_blob() {
386        let delivery_queue = zx::Vmo::create(65536).unwrap();
387        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
388            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
389            PENDING_DELIVERY_COMMANDS_CAPACITY,
390        )
391        .unwrap();
392
393        let verifier = Arc::new(Verifier::new(delivery_queue));
394        let key = 42u64;
395        let leaves = [[0xABu8; 32], [0xCDu8; 32]];
396
397        verifier.register_blob(key, &leaves, 96 * 1024).expect("register_blob failed");
398
399        let msg = receiver.peek().expect("peek msg");
400        assert_eq!(msg.opcode, mapping::DELIVERY_REGISTER_BLOB_COMMAND);
401        assert_eq!(msg.key, key);
402        assert_eq!(msg.length, 64);
403        assert_eq!(msg.read_alignment, 96 * 1024);
404        let mut buf = vec![0u8; 64];
405        msg.payload_slice(msg.offset, msg.length).copy_to_slice(&mut buf);
406        assert_eq!(&buf[..32], &[0xABu8; 32]);
407        assert_eq!(&buf[32..], &[0xCDu8; 32]);
408        msg.pop().expect("pop msg failed");
409    }
410
411    #[fuchsia::test]
412    fn test_verifier_buffer_96k_overlap() {
413        let total_size = 128 * 1024;
414        let chunk_size = 32 * 1024;
415        let delivery_queue = zx::Vmo::create(total_size as u64 * 2 + 65536).unwrap();
416        let mut receiver = vmo_fifo::Receiver::<RawDeliveryCommand>::new(
417            delivery_queue.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
418            PENDING_DELIVERY_COMMANDS_CAPACITY,
419        )
420        .unwrap();
421
422        let verifier = Arc::new(Verifier::new(delivery_queue));
423        let key = 45u64;
424
425        let mut request = verifier.get_page_request(key, 0..total_size as u64);
426        request.prepare(0..total_size as u64, chunk_size).expect("prepare");
427
428        let chunk = vec![0x55u8; chunk_size];
429
430        // First two 32 KiB chunks (64 KiB total) should not flush yet.
431        for _ in 0..2 {
432            request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
433            request.commit(chunk_size).expect("commit");
434        }
435        assert!(receiver.is_empty());
436
437        // Third 32 KiB chunk reaches 96 KiB and flushes 96 KiB so verification overlaps with
438        // decompressing the final 32 KiB.
439        request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
440        request.commit(chunk_size).expect("commit");
441
442        let msg1 = receiver.peek().expect("msg1");
443        assert_eq!(msg1.target_offset, 0);
444        assert_eq!(msg1.length, 96 * 1024);
445        msg1.pop().expect("pop msg1");
446
447        // Fourth 32 KiB chunk completes the 128 KiB window and flushes the remaining 32 KiB.
448        request.mut_ptr_slice().subslice_mut(0..chunk_size).copy_from_slice(&chunk);
449        request.commit(chunk_size).expect("commit");
450
451        let msg2 = receiver.peek().expect("msg2");
452        assert_eq!(msg2.target_offset, 96 * 1024);
453        assert_eq!(msg2.length, 32 * 1024);
454        msg2.pop().expect("pop msg2");
455    }
456}