Skip to main content

test_vmo_backed_block_server/
data_for_testing.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 block_server::async_interface::Interface;
6use block_server::{DeviceInfo, ReadOptions, WriteFlags, WriteOptions};
7use fuchsia_sync::{Mutex, MutexGuard};
8use fxfs_crypto::{FscryptSoftwareInoLblk32FileCipher, UnwrappedKey};
9use rand::Rng as _;
10use std::borrow::Cow;
11use std::collections::BTreeMap;
12use std::num::NonZero;
13use std::sync::Arc;
14use std::time::Duration;
15
16pub struct Data {
17    pub info: DeviceInfo,
18    pub block_size: u32,
19    pub data: zx::Vmo,
20    pub write_cache: Option<Mutex<WriteCache>>,
21    pub max_jitter_usec: Option<u64>,
22    pub observer: Option<Box<dyn Observer>>,
23    // Maps keyslots to lblk32 software ciphers used to encrypt/decrypt file contents.
24    pub fscrypt_keys: Mutex<FscryptKeys>,
25}
26
27impl Data {
28    async fn add_jitter(&self) {
29        if let Some(max) = self.max_jitter_usec {
30            fuchsia_async::Timer::new(Duration::from_micros(rand::random_range(0..max))).await
31        }
32    }
33
34    fn write_cache(&self) -> Option<MutexGuard<'_, WriteCache>> {
35        self.write_cache.as_ref().map(Mutex::lock)
36    }
37
38    pub fn client_closed(&self) -> Result<(), zx::Status> {
39        if let Some(mut cache) = self.write_cache() {
40            if let Some(observer) = self.observer.as_ref() {
41                observer.close(Some(&mut *cache));
42            }
43            cache.apply(&self.data)
44        } else {
45            if let Some(observer) = self.observer.as_ref() {
46                observer.close(None);
47            }
48            Ok(())
49        }
50    }
51
52    pub fn fscrypt_keys(&self) -> MutexGuard<'_, FscryptKeys> {
53        self.fscrypt_keys.lock()
54    }
55}
56
57impl Interface for Data {
58    fn get_info(&self) -> Cow<'_, DeviceInfo> {
59        Cow::Borrowed(&self.info)
60    }
61
62    async fn read(
63        &self,
64        device_block_offset: u64,
65        block_count: u32,
66        vmo: &Arc<zx::Vmo>,
67        vmo_offset: u64,
68        opts: ReadOptions,
69        _trace_flow_id: Option<NonZero<u64>>,
70    ) -> Result<(), zx::Status> {
71        self.add_jitter().await;
72        if let Some(observer) = self.observer.as_ref() {
73            observer.read(device_block_offset, block_count, vmo, vmo_offset);
74        }
75
76        if let Some(max) = self.info.max_transfer_blocks().as_ref() {
77            // Requests should be split up by the core library
78            assert!(block_count <= max.get());
79        }
80        if device_block_offset + block_count as u64 > self.info.block_count().unwrap() {
81            Err(zx::Status::OUT_OF_RANGE)
82        } else {
83            let mut data = if let Some(cache) = self.write_cache() {
84                let mut data = vec![0u8; block_count as usize * self.block_size as usize];
85                cache.read(&self.data, device_block_offset, &mut data[..])?;
86                data
87            } else {
88                self.data.read_to_vec(
89                    device_block_offset * self.block_size as u64,
90                    block_count as u64 * self.block_size as u64,
91                )?
92            };
93            if opts.inline_crypto.is_enabled {
94                self.fscrypt_keys()
95                    .get_key(opts.inline_crypto.slot)?
96                    .decrypt(&mut data, opts.inline_crypto.dun as u128)
97                    .map_err(|_| zx::Status::IO)?;
98            }
99            vmo.write(&data[..], vmo_offset)
100        }
101    }
102
103    async fn write(
104        &self,
105        device_block_offset: u64,
106        block_count: u32,
107        vmo: &Arc<zx::Vmo>,
108        vmo_offset: u64,
109        opts: WriteOptions,
110        _trace_flow_id: Option<NonZero<u64>>,
111    ) -> Result<(), zx::Status> {
112        self.add_jitter().await;
113        if let Some(observer) = self.observer.as_ref() {
114            match observer.write(device_block_offset, block_count, vmo, vmo_offset, opts) {
115                WriteAction::Write => {}
116                WriteAction::Discard => return Ok(()),
117                WriteAction::Fail => return Err(zx::Status::IO),
118            }
119        }
120
121        if opts.flags.contains(WriteFlags::PRE_BARRIER) {
122            if let Some(mut cache) = self.write_cache() {
123                cache.apply(&self.data)?;
124            }
125        }
126        if let Some(max) = self.info.max_transfer_blocks().as_ref() {
127            // Requests should be split up by the core library
128            assert!(block_count <= max.get());
129        }
130        if device_block_offset + block_count as u64 > self.info.block_count().unwrap() {
131            Err(zx::Status::OUT_OF_RANGE)
132        } else {
133            let mut data =
134                vmo.read_to_vec(vmo_offset, block_count as u64 * self.block_size as u64)?;
135            if !opts.flags.contains(WriteFlags::FORCE_ACCESS)
136                && let Some(mut cache) = self.write_cache()
137            {
138                cache.insert(device_block_offset, &data[..]);
139            }
140            if opts.inline_crypto.is_enabled {
141                self.fscrypt_keys()
142                    .get_key(opts.inline_crypto.slot)?
143                    .encrypt(&mut data, opts.inline_crypto.dun as u128)
144                    .map_err(|_| zx::Status::IO)?;
145            }
146            self.data.write(&data[..], device_block_offset * self.block_size as u64)
147        }
148    }
149
150    async fn flush(&self, _trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
151        self.add_jitter().await;
152
153        let mut cache = self.write_cache();
154        if let Some(observer) = self.observer.as_ref() {
155            match cache.as_mut() {
156                Some(w) => observer.flush(Some(&mut *w)),
157                None => observer.flush(None),
158            }
159        }
160        if let Some(w) = cache.as_mut() { w.apply(&self.data) } else { Ok(()) }
161    }
162
163    async fn trim(
164        &self,
165        device_block_offset: u64,
166        block_count: u32,
167        _trace_flow_id: Option<NonZero<u64>>,
168    ) -> Result<(), zx::Status> {
169        self.add_jitter().await;
170
171        if let Some(observer) = self.observer.as_ref() {
172            observer.trim(device_block_offset, block_count);
173        }
174        if device_block_offset + block_count as u64 > self.info.block_count().unwrap() {
175            Err(zx::Status::OUT_OF_RANGE)
176        } else {
177            Ok(())
178        }
179    }
180}
181
182/// Keeps track of a sequence of writes since the last flush or barrier, and allows them to be
183/// arbitrarily modified or re-ordered.
184pub struct WriteCache {
185    block_size: u64,
186    block_offsets: Vec<u64>,
187    buffer: Vec<u8>,
188}
189
190impl WriteCache {
191    pub(crate) fn new(block_size: u64) -> Self {
192        Self { block_size, block_offsets: vec![], buffer: vec![] }
193    }
194
195    fn insert(&mut self, block_offset: u64, contents: &[u8]) {
196        let block_count = contents.len() as u64 / self.block_size;
197        let mut buf_offset = 0;
198        for offset in block_offset..block_offset + block_count {
199            self.block_offsets.push(offset);
200            self.buffer
201                .extend_from_slice(&contents[buf_offset..buf_offset + self.block_size as usize]);
202            buf_offset += self.block_size as usize;
203        }
204    }
205
206    // Reads the last written value, falling back to `data` if there are no local updates.
207    fn read(
208        &self,
209        data: &zx::Vmo,
210        block_offset: u64,
211        contents: &mut [u8],
212    ) -> Result<(), zx::Status> {
213        let block_count = contents.len() as u64 / self.block_size;
214        let max_offset = block_offset + block_count;
215        data.read(contents, block_offset * self.block_size)?;
216        // Apply any buffered writes that would overwrite the actual contents.  If the same offset
217        // shows up multiple times, we want to use the most recent write, so it's important to
218        // iterate in order.
219        for (idx, offset) in self.block_offsets.iter().enumerate() {
220            if *offset >= block_offset && *offset < max_offset {
221                let in_offset = idx * self.block_size as usize;
222                let out_offset = ((*offset - block_offset) * self.block_size) as usize;
223                contents[out_offset..out_offset + self.block_size as usize]
224                    .copy_from_slice(&self.buffer[in_offset..in_offset + self.block_size as usize]);
225            }
226        }
227        Ok(())
228    }
229
230    // Persists all writes to `data` and empties the cache.
231    fn apply(&mut self, data: &zx::Vmo) -> Result<(), zx::Status> {
232        let mut buf_offset = 0;
233        for offset in self.block_offsets.drain(..) {
234            data.write(
235                &self.buffer[buf_offset..buf_offset + self.block_size as usize],
236                offset * self.block_size,
237            )?;
238            buf_offset += self.block_size as usize;
239        }
240        self.buffer.clear();
241        Ok(())
242    }
243
244    /// Returns the number of writes in the batch.
245    pub fn len(&self) -> usize {
246        self.block_offsets.len()
247    }
248
249    /// Returns an iterator over the batch of writes (in temporal sequence).
250    pub fn iter(&self) -> impl Iterator<Item = (&u64, &[u8])> {
251        self.block_offsets.iter().zip(self.buffer.windows(self.block_size as usize))
252    }
253
254    fn swap_writes(&mut self, i: usize, j: usize) {
255        self.block_offsets.swap(i, j);
256        let bs = self.block_size as usize;
257        let mut buf = vec![0u8; bs];
258        buf.copy_from_slice(&self.buffer[i * bs..(i + 1) * bs]);
259        self.buffer.copy_within(j * bs..(j + 1) * bs, i * bs);
260        self.buffer[j * bs..(j + 1) * bs].copy_from_slice(&buf[..]);
261    }
262
263    /// Reorders all writes.
264    pub fn shuffle(&mut self) {
265        // Implements the Fisher-Yates shuffle.
266        let mut rng = rand::rng();
267        for i in 0..self.block_offsets.len() {
268            let j = rng.random_range(0..=i);
269            if i != j {
270                self.swap_writes(i, j);
271            }
272        }
273    }
274
275    /// Discards a random number of writes from the tail, simulating a power-cut.
276    pub fn discard_some(&mut self) {
277        let mut rng = rand::rng();
278        let idx = rng.random_range(0..=self.block_offsets.len());
279        for i in idx..self.block_offsets.len() {
280            self.buffer[i * self.block_size as usize..(i + 1) * self.block_size as usize]
281                .fill(0xab);
282        }
283    }
284}
285
286/// The Observer can silently discard writes, or fail them explicitly (zx::Status::IO is returned).
287pub enum WriteAction {
288    Write,
289    Discard,
290    Fail,
291}
292
293pub trait Observer: Send + Sync {
294    fn read(
295        &self,
296        _device_block_offset: u64,
297        _block_count: u32,
298        _vmo: &Arc<zx::Vmo>,
299        _vmo_offset: u64,
300    ) {
301    }
302
303    fn write(
304        &self,
305        _device_block_offset: u64,
306        _block_count: u32,
307        _vmo: &Arc<zx::Vmo>,
308        _vmo_offset: u64,
309        _opts: WriteOptions,
310    ) -> WriteAction {
311        WriteAction::Write
312    }
313
314    // If [`VmoBackedServerOptions::write_tracking`] is enabled, `writes` is set to the batch since
315    // last flush or barrier and can be freely modified.
316    fn flush(&self, _writes: Option<&mut WriteCache>) {}
317
318    // If [`VmoBackedServerOptions::write_tracking`] is enabled, `writes` is set to the batch since
319    // last flush or barrier and can be freely modified.
320    fn close(&self, _writes: Option<&mut WriteCache>) {}
321
322    fn trim(&self, _device_block_offset: u64, _block_count: u32) {}
323}
324
325pub struct FscryptKeys(BTreeMap<u8, FscryptSoftwareInoLblk32FileCipher>);
326
327impl FscryptKeys {
328    pub fn new() -> Self {
329        Self(BTreeMap::new())
330    }
331
332    pub fn evict_key(&mut self, slot: u8) -> Result<(), zx::Status> {
333        match self.0.remove(&slot) {
334            Some(_) => Ok(()),
335            None => Err(zx::Status::INVALID_ARGS),
336        }
337    }
338
339    pub fn program_key(&mut self, xts_key: &[u8; 64]) -> Result<u8, zx::Status> {
340        let unwrapped_key = UnwrappedKey::new(xts_key.to_vec());
341        let cipher = FscryptSoftwareInoLblk32FileCipher::new(&unwrapped_key);
342        // Find the first keyslot that is not in use and use it.
343        for slot in 0..=u8::MAX {
344            if self.0.contains_key(&slot) {
345                continue;
346            }
347            self.0.insert(slot, cipher);
348            return Ok(slot);
349        }
350        Err(zx::Status::NO_RESOURCES)
351    }
352
353    pub fn get_key(&self, slot: u8) -> Result<&FscryptSoftwareInoLblk32FileCipher, zx::Status> {
354        self.0.get(&slot).ok_or(zx::Status::IO)
355    }
356}