fake_block_server/
fake_server.rs

1// Copyright 2024 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 anyhow::Error;
6use block_server::async_interface::{Interface, SessionManager};
7use block_server::{BlockServer, DeviceInfo, PartitionInfo, WriteOptions};
8use fidl::endpoints::{Proxy as _, ServerEnd};
9use std::borrow::Cow;
10use std::num::NonZero;
11use std::sync::Arc;
12use {
13    fidl_fuchsia_hardware_block as fblock, fidl_fuchsia_hardware_block_volume as fvolume,
14    fuchsia_async as fasync,
15};
16
17pub const TYPE_GUID: [u8; 16] = [1; 16];
18pub const INSTANCE_GUID: [u8; 16] = [2; 16];
19pub const PARTITION_NAME: &str = "fake-server";
20
21/// The Observer can silently discard writes, or fail them explicitly (zx::Status::IO is returned).
22pub enum WriteAction {
23    Write,
24    Discard,
25    Fail,
26}
27
28pub trait Observer: Send + Sync {
29    fn read(
30        &self,
31        _device_block_offset: u64,
32        _block_count: u32,
33        _vmo: &Arc<zx::Vmo>,
34        _vmo_offset: u64,
35    ) {
36    }
37
38    fn write(
39        &self,
40        _device_block_offset: u64,
41        _block_count: u32,
42        _vmo: &Arc<zx::Vmo>,
43        _vmo_offset: u64,
44        _opts: WriteOptions,
45    ) -> WriteAction {
46        WriteAction::Write
47    }
48
49    fn flush(&self) {}
50
51    fn trim(&self, _device_block_offset: u64, _block_count: u32) {}
52}
53
54pub struct FakeServer {
55    server: BlockServer<SessionManager<Data>>,
56}
57
58pub struct FakeServerOptions<'a> {
59    pub flags: fblock::Flag,
60    pub block_count: Option<u64>,
61    pub block_size: u32,
62    pub max_transfer_blocks: Option<NonZero<u32>>,
63    pub initial_content: Option<&'a [u8]>,
64    pub vmo: Option<zx::Vmo>,
65    pub observer: Option<Box<dyn Observer>>,
66}
67
68impl Default for FakeServerOptions<'_> {
69    fn default() -> Self {
70        FakeServerOptions {
71            flags: fblock::Flag::empty(),
72            block_count: None,
73            block_size: 512,
74            max_transfer_blocks: None,
75            initial_content: None,
76            vmo: None,
77            observer: None,
78        }
79    }
80}
81
82impl From<FakeServerOptions<'_>> for FakeServer {
83    fn from(options: FakeServerOptions<'_>) -> Self {
84        let (vmo, block_count) = if let Some(vmo) = options.vmo {
85            let size = vmo.get_size().unwrap();
86            debug_assert!(size % options.block_size as u64 == 0);
87            let block_count = size / options.block_size as u64;
88            if let Some(bc) = options.block_count {
89                assert_eq!(block_count, bc);
90            }
91            (vmo, block_count)
92        } else {
93            let block_count = options.block_count.unwrap();
94            (zx::Vmo::create(block_count * options.block_size as u64).unwrap(), block_count)
95        };
96
97        if let Some(initial_content) = options.initial_content {
98            vmo.write(initial_content, 0).unwrap();
99        }
100
101        Self {
102            server: BlockServer::new(
103                options.block_size,
104                Arc::new(Data {
105                    flags: options.flags,
106                    block_size: options.block_size,
107                    block_count: block_count,
108                    max_transfer_blocks: options.max_transfer_blocks,
109                    data: vmo,
110                    observer: options.observer,
111                }),
112            ),
113        }
114    }
115}
116
117impl FakeServer {
118    pub fn new(block_count: u64, block_size: u32, initial_content: &[u8]) -> Self {
119        FakeServerOptions {
120            block_count: Some(block_count),
121            block_size,
122            initial_content: Some(initial_content),
123            ..Default::default()
124        }
125        .into()
126    }
127
128    pub fn from_vmo(block_size: u32, vmo: zx::Vmo) -> Self {
129        FakeServerOptions { block_size, vmo: Some(vmo), ..Default::default() }.into()
130    }
131
132    pub async fn serve(&self, requests: fvolume::VolumeRequestStream) -> Result<(), Error> {
133        self.server.handle_requests(requests).await
134    }
135
136    pub fn volume_proxy(self: &Arc<Self>) -> fvolume::VolumeProxy {
137        let (client, server) = fidl::endpoints::create_endpoints();
138        self.connect(server);
139        client.into_proxy()
140    }
141
142    pub fn connect(self: &Arc<Self>, server: ServerEnd<fvolume::VolumeMarker>) {
143        let this = self.clone();
144        fasync::Task::spawn(async move {
145            let _ = this.serve(server.into_stream()).await;
146        })
147        .detach();
148    }
149
150    pub fn block_proxy(self: &Arc<Self>) -> fblock::BlockProxy {
151        fblock::BlockProxy::from_channel(self.volume_proxy().into_channel().unwrap())
152    }
153}
154
155struct Data {
156    flags: fblock::Flag,
157    block_size: u32,
158    block_count: u64,
159    max_transfer_blocks: Option<NonZero<u32>>,
160    data: zx::Vmo,
161    observer: Option<Box<dyn Observer>>,
162}
163
164impl Interface for Data {
165    async fn get_info(&self) -> Result<Cow<'_, DeviceInfo>, zx::Status> {
166        Ok(Cow::Owned(DeviceInfo::Partition(PartitionInfo {
167            device_flags: self.flags,
168            max_transfer_blocks: self.max_transfer_blocks.clone(),
169            block_range: Some(0..self.block_count),
170            type_guid: TYPE_GUID.clone(),
171            instance_guid: INSTANCE_GUID.clone(),
172            name: PARTITION_NAME.to_string(),
173            flags: 0u64,
174        })))
175    }
176
177    async fn read(
178        &self,
179        device_block_offset: u64,
180        block_count: u32,
181        vmo: &Arc<zx::Vmo>,
182        vmo_offset: u64,
183        _trace_flow_id: Option<NonZero<u64>>,
184    ) -> Result<(), zx::Status> {
185        if let Some(observer) = self.observer.as_ref() {
186            observer.read(device_block_offset, block_count, vmo, vmo_offset);
187        }
188        if let Some(max) = self.max_transfer_blocks.as_ref() {
189            // Requests should be split up by the core library
190            assert!(block_count <= max.get());
191        }
192        if device_block_offset + block_count as u64 > self.block_count {
193            Err(zx::Status::OUT_OF_RANGE)
194        } else {
195            vmo.write(
196                &self.data.read_to_vec(
197                    device_block_offset * self.block_size as u64,
198                    block_count as u64 * self.block_size as u64,
199                )?,
200                vmo_offset,
201            )
202        }
203    }
204
205    async fn write(
206        &self,
207        device_block_offset: u64,
208        block_count: u32,
209        vmo: &Arc<zx::Vmo>,
210        vmo_offset: u64,
211        opts: WriteOptions,
212        _trace_flow_id: Option<NonZero<u64>>,
213    ) -> Result<(), zx::Status> {
214        if let Some(observer) = self.observer.as_ref() {
215            match observer.write(device_block_offset, block_count, vmo, vmo_offset, opts) {
216                WriteAction::Write => {}
217                WriteAction::Discard => return Ok(()),
218                WriteAction::Fail => return Err(zx::Status::IO),
219            }
220        }
221        if let Some(max) = self.max_transfer_blocks.as_ref() {
222            // Requests should be split up by the core library
223            assert!(block_count <= max.get());
224        }
225        if device_block_offset + block_count as u64 > self.block_count {
226            Err(zx::Status::OUT_OF_RANGE)
227        } else {
228            self.data.write(
229                &vmo.read_to_vec(vmo_offset, block_count as u64 * self.block_size as u64)?,
230                device_block_offset * self.block_size as u64,
231            )
232        }
233    }
234
235    async fn flush(&self, _trace_flow_id: Option<NonZero<u64>>) -> Result<(), zx::Status> {
236        if let Some(observer) = self.observer.as_ref() {
237            observer.flush();
238        }
239        Ok(())
240    }
241
242    async fn trim(
243        &self,
244        device_block_offset: u64,
245        block_count: u32,
246        _trace_flow_id: Option<NonZero<u64>>,
247    ) -> Result<(), zx::Status> {
248        if let Some(observer) = self.observer.as_ref() {
249            observer.trim(device_block_offset, block_count);
250        }
251        if device_block_offset + block_count as u64 > self.block_count {
252            Err(zx::Status::OUT_OF_RANGE)
253        } else {
254            Ok(())
255        }
256    }
257}