1use blob_writer::BlobWriter;
6use block_client::{BlockClient as _, RemoteBlockClient};
7use delivery_blob::{CompressionMode, Type1Blob};
8use fake_block_server::FakeServer;
9use fidl::endpoints::Proxy as _;
10use fidl_fuchsia_fs_startup::{CreateOptions, MountOptions};
11use fidl_fuchsia_fxfs::{BlobCreatorProxy, CryptManagementMarker, CryptMarker, KeyPurpose};
12use fs_management::filesystem::{
13 BlockConnector, DirBasedBlockConnector, Filesystem, ServingMultiVolumeFilesystem,
14};
15use fs_management::format::constants::{F2FS_MAGIC, FXFS_MAGIC, MINFS_MAGIC};
16use fs_management::{Fvm, Fxfs, BLOBFS_TYPE_GUID, DATA_TYPE_GUID, FVM_TYPE_GUID};
17use fuchsia_component::client::connect_to_protocol_at_dir_svc;
18use fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route};
19use fuchsia_hash::Hash;
20use gpt_component::gpt::GptManager;
21use key_bag::Aes256Key;
22use std::ops::Deref;
23use std::sync::Arc;
24use storage_isolated_driver_manager::fvm::format_for_fvm;
25use uuid::Uuid;
26use vfs::directory::entry_container::Directory;
27use vfs::execution_scope::ExecutionScope;
28use vfs::path::Path;
29use vfs::ObjectRequest;
30use zerocopy::{Immutable, IntoBytes};
31use zx::{self as zx, HandleBased};
32use {fidl_fuchsia_io as fio, fidl_fuchsia_logger as flogger, fuchsia_async as fasync};
33
34pub const TEST_DISK_BLOCK_SIZE: u32 = 512;
35pub const FVM_SLICE_SIZE: u64 = 32 * 1024;
36
37pub const DEFAULT_DATA_VOLUME_SIZE: u64 = 101 * 1024 * 1024;
46pub const DEFAULT_REMAINING_VOLUME_SIZE: u64 = 4 * 1024 * 1024;
47pub const DEFAULT_DISK_SIZE: u64 = DEFAULT_DATA_VOLUME_SIZE + DEFAULT_REMAINING_VOLUME_SIZE;
50
51const KEY_BAG_CONTENTS: &'static str = "\
56{
57 \"version\":1,
58 \"keys\": {
59 \"0\":{
60 \"Aes128GcmSivWrapped\": [
61 \"7a7c6a718cfde7078f6edec5\",
62 \"7cc31b765c74db3191e269d2666267022639e758fe3370e8f36c166d888586454fd4de8aeb47aadd81c531b0a0a66f27\"
63 ]
64 },
65 \"1\":{
66 \"Aes128GcmSivWrapped\": [
67 \"b7d7f459cbee4cc536cc4324\",
68 \"9f6a5d894f526b61c5c091e5e02a7ff94d18e6ad36a0aa439c86081b726eca79e6b60bd86ee5d86a20b3df98f5265a99\"
69 ]
70 }
71 }
72}";
73
74pub const BLOB_CONTENTS: [u8; 1000] = [1; 1000];
75
76const DATA_KEY: Aes256Key = Aes256Key::create([
77 0xcf, 0x9e, 0x45, 0x2a, 0x22, 0xa5, 0x70, 0x31, 0x33, 0x3b, 0x4d, 0x6b, 0x6f, 0x78, 0x58, 0x29,
78 0x04, 0x79, 0xc7, 0xd6, 0xa9, 0x4b, 0xce, 0x82, 0x04, 0x56, 0x5e, 0x82, 0xfc, 0xe7, 0x37, 0xa8,
79]);
80
81const METADATA_KEY: Aes256Key = Aes256Key::create([
82 0x0f, 0x4d, 0xca, 0x6b, 0x35, 0x0e, 0x85, 0x6a, 0xb3, 0x8c, 0xdd, 0xe9, 0xda, 0x0e, 0xc8, 0x22,
83 0x8e, 0xea, 0xd8, 0x05, 0xc4, 0xc9, 0x0b, 0xa8, 0xd8, 0x85, 0x87, 0x50, 0x75, 0x40, 0x1c, 0x4c,
84]);
85
86const FVM_PART_INSTANCE_GUID: [u8; 16] = [3u8; 16];
87const DEFAULT_TEST_TYPE_GUID: [u8; 16] = [
88 0x66, 0x73, 0x68, 0x6F, 0x73, 0x74, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x73,
89];
90
91async fn create_hermetic_crypt_service(
92 data_key: Aes256Key,
93 metadata_key: Aes256Key,
94) -> RealmInstance {
95 let builder = RealmBuilder::new().await.unwrap();
96 let url = "#meta/fxfs-crypt.cm";
97 let crypt = builder.add_child("fxfs-crypt", url, ChildOptions::new().eager()).await.unwrap();
98 builder
99 .add_route(
100 Route::new()
101 .capability(Capability::protocol::<CryptMarker>())
102 .capability(Capability::protocol::<CryptManagementMarker>())
103 .from(&crypt)
104 .to(Ref::parent()),
105 )
106 .await
107 .unwrap();
108 builder
109 .add_route(
110 Route::new()
111 .capability(Capability::protocol::<flogger::LogSinkMarker>())
112 .from(Ref::parent())
113 .to(&crypt),
114 )
115 .await
116 .unwrap();
117 let realm = builder.build().await.expect("realm build failed");
118 let crypt_management =
119 realm.root.connect_to_protocol_at_exposed_dir::<CryptManagementMarker>().unwrap();
120 let wrapping_key_id_0 = [0; 16];
121 let mut wrapping_key_id_1 = [0; 16];
122 wrapping_key_id_1[0] = 1;
123 crypt_management
124 .add_wrapping_key(&wrapping_key_id_0, data_key.deref())
125 .await
126 .unwrap()
127 .expect("add_wrapping_key failed");
128 crypt_management
129 .add_wrapping_key(&wrapping_key_id_1, metadata_key.deref())
130 .await
131 .unwrap()
132 .expect("add_wrapping_key failed");
133 crypt_management
134 .set_active_key(KeyPurpose::Data, &wrapping_key_id_0)
135 .await
136 .unwrap()
137 .expect("set_active_key failed");
138 crypt_management
139 .set_active_key(KeyPurpose::Metadata, &wrapping_key_id_1)
140 .await
141 .unwrap()
142 .expect("set_active_key failed");
143 realm
144}
145
146pub async fn write_test_blob(blob_creator: BlobCreatorProxy, data: &[u8]) -> Hash {
148 let hash = fuchsia_merkle::from_slice(data).root();
149 let compressed_data = Type1Blob::generate(&data, CompressionMode::Always);
150
151 let blob_writer_client_end = blob_creator
152 .create(&hash.into(), false)
153 .await
154 .expect("transport error on create")
155 .expect("failed to create blob");
156
157 let writer = blob_writer_client_end.into_proxy();
158 let mut blob_writer = BlobWriter::create(writer, compressed_data.len() as u64)
159 .await
160 .expect("failed to create BlobWriter");
161 blob_writer.write(&compressed_data).await.unwrap();
162 hash
163}
164
165pub enum Disk {
166 Prebuilt(zx::Vmo, Option<[u8; 16]>),
167 Builder(DiskBuilder),
168}
169
170impl Disk {
171 pub async fn into_vmo_and_type_guid(self) -> (zx::Vmo, Option<[u8; 16]>) {
172 match self {
173 Disk::Prebuilt(vmo, guid) => (vmo, guid),
174 Disk::Builder(builder) => builder.build().await,
175 }
176 }
177
178 pub fn builder(&mut self) -> &mut DiskBuilder {
179 match self {
180 Disk::Prebuilt(..) => panic!("attempted to get builder for prebuilt disk"),
181 Disk::Builder(builder) => builder,
182 }
183 }
184}
185
186#[derive(Debug, Default)]
187pub struct DataSpec {
188 pub format: Option<&'static str>,
189 pub zxcrypt: bool,
190}
191
192#[derive(Debug)]
193pub struct VolumesSpec {
194 pub fxfs_blob: bool,
195 pub create_data_partition: bool,
196}
197
198enum FxfsType {
199 Fxfs(Box<dyn BlockConnector>),
200 FxBlob(ServingMultiVolumeFilesystem, RealmInstance),
201}
202
203#[derive(Debug)]
204pub struct DiskBuilder {
205 size: u64,
206 uninitialized: bool,
208 blob_hash: Option<Hash>,
209 data_volume_size: u64,
210 data_spec: DataSpec,
211 volumes_spec: VolumesSpec,
212 corrupt_data: bool,
214 gpt: bool,
215 extra_volumes: Vec<&'static str>,
216 format_volume_manager: bool,
218 legacy_data_label: bool,
219 fs_switch: Option<String>,
221 type_guid: Option<[u8; 16]>,
223}
224
225impl DiskBuilder {
226 pub fn uninitialized() -> DiskBuilder {
227 Self { uninitialized: true, type_guid: None, ..Self::new() }
228 }
229
230 pub fn new() -> DiskBuilder {
231 DiskBuilder {
232 size: DEFAULT_DISK_SIZE,
233 uninitialized: false,
234 blob_hash: None,
235 data_volume_size: DEFAULT_DATA_VOLUME_SIZE,
236 data_spec: DataSpec { format: None, zxcrypt: false },
237 volumes_spec: VolumesSpec { fxfs_blob: false, create_data_partition: true },
238 corrupt_data: false,
239 gpt: false,
240 extra_volumes: Vec::new(),
241 format_volume_manager: true,
242 legacy_data_label: false,
243 fs_switch: None,
244 type_guid: Some(DEFAULT_TEST_TYPE_GUID),
245 }
246 }
247
248 pub fn set_uninitialized(&mut self) -> &mut Self {
249 self.uninitialized = true;
250 self
251 }
252
253 pub fn size(&mut self, size: u64) -> &mut Self {
254 self.size = size;
255 self
256 }
257
258 pub fn data_volume_size(&mut self, data_volume_size: u64) -> &mut Self {
259 assert_eq!(
260 data_volume_size % FVM_SLICE_SIZE,
261 0,
262 "data_volume_size {} needs to be a multiple of fvm slice size {}",
263 data_volume_size,
264 FVM_SLICE_SIZE
265 );
266 self.data_volume_size = data_volume_size;
267 self.size = self.size.max(self.data_volume_size + DEFAULT_REMAINING_VOLUME_SIZE);
271 self
272 }
273
274 pub fn format_volumes(&mut self, volumes_spec: VolumesSpec) -> &mut Self {
275 self.volumes_spec = volumes_spec;
276 self
277 }
278
279 pub fn format_data(&mut self, data_spec: DataSpec) -> &mut Self {
280 log::info!(data_spec:?; "formatting data volume");
281 if !self.volumes_spec.fxfs_blob {
282 assert!(self.format_volume_manager);
283 } else {
284 if let Some(format) = data_spec.format {
285 assert_eq!(format, "fxfs");
286 }
287 }
288 self.data_spec = data_spec;
289 self
290 }
291
292 pub fn set_fs_switch(&mut self, content: &str) -> &mut Self {
293 self.fs_switch = Some(content.to_string());
294 self
295 }
296
297 pub fn corrupt_data(&mut self) -> &mut Self {
298 self.corrupt_data = true;
299 self
300 }
301
302 pub fn with_gpt(&mut self) -> &mut Self {
303 self.gpt = true;
304 self.type_guid = None;
307 self
308 }
309
310 pub fn with_extra_volume(&mut self, volume_name: &'static str) -> &mut Self {
311 self.extra_volumes.push(volume_name);
312 self
313 }
314
315 pub fn with_unformatted_volume_manager(&mut self) -> &mut Self {
316 assert!(self.data_spec.format.is_none());
317 self.format_volume_manager = false;
318 self
319 }
320
321 pub fn with_legacy_data_label(&mut self) -> &mut Self {
322 self.legacy_data_label = true;
323 self
324 }
325
326 pub async fn build(mut self) -> (zx::Vmo, Option<[u8; 16]>) {
327 log::info!("building disk: {:?}", self);
328 let vmo = zx::Vmo::create(self.size).unwrap();
329
330 if self.uninitialized {
331 return (vmo, self.type_guid);
332 }
333
334 let server = Arc::new(FakeServer::from_vmo(
335 TEST_DISK_BLOCK_SIZE,
336 vmo.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
337 ));
338
339 if self.gpt {
340 let client = Arc::new(RemoteBlockClient::new(server.block_proxy()).await.unwrap());
342 let _ = gpt::Gpt::format(
343 client,
344 vec![gpt::PartitionInfo {
345 label: "fvm".to_string(),
346 type_guid: gpt::Guid::from_bytes(FVM_TYPE_GUID),
347 instance_guid: gpt::Guid::from_bytes(FVM_PART_INSTANCE_GUID),
348 start_block: 64,
349 num_blocks: self.size / TEST_DISK_BLOCK_SIZE as u64 - 128,
350 flags: 0,
351 }],
352 )
353 .await
354 .expect("gpt format failed");
355 }
356
357 if !self.format_volume_manager {
358 return (vmo, self.type_guid);
359 }
360
361 let mut gpt = None;
362 let connector: Box<dyn BlockConnector> = if self.gpt {
363 let partitions_dir = vfs::directory::immutable::simple();
365 let manager =
366 GptManager::new(server.block_proxy(), partitions_dir.clone()).await.unwrap();
367 let scope = ExecutionScope::new();
368 let (dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
369 let flags = fio::Flags::PROTOCOL_DIRECTORY | fio::PERM_READABLE | fio::PERM_WRITABLE;
370 ObjectRequest::new(flags, &fio::Options::default(), server_end.into_channel())
371 .handle(|request| partitions_dir.open3(scope.clone(), Path::dot(), flags, request));
372 gpt = Some(manager);
373 Box::new(DirBasedBlockConnector::new(dir, String::from("part-000/volume")))
374 } else {
375 Box::new(move |server_end| Ok(server.connect(server_end)))
377 };
378
379 if self.volumes_spec.fxfs_blob {
380 self.build_fxfs_as_volume_manager(connector).await;
381 } else {
382 self.build_fvm_as_volume_manager(connector).await;
383 }
384 if let Some(gpt) = gpt {
385 gpt.shutdown().await;
386 }
387 (vmo, self.type_guid)
388 }
389
390 async fn build_fxfs_as_volume_manager(&mut self, connector: Box<dyn BlockConnector>) {
391 let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
392 let mut fxfs = Filesystem::from_boxed_config(connector, Box::new(Fxfs::default()));
393 fxfs.format().await.expect("format failed");
395 let mut fs = fxfs.serve_multi_volume().await.expect("serve_multi_volume failed");
396 let blob_volume = fs
397 .create_volume(
398 "blob",
399 CreateOptions::default(),
400 MountOptions { as_blob: Some(true), ..MountOptions::default() },
401 )
402 .await
403 .expect("failed to create blob volume");
404 let blob_creator = connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
405 blob_volume.exposed_dir(),
406 )
407 .expect("failed to connect to the Blob service");
408 self.blob_hash = Some(write_test_blob(blob_creator, &BLOB_CONTENTS).await);
409
410 for volume in &self.extra_volumes {
411 fs.create_volume(volume, CreateOptions::default(), MountOptions::default())
412 .await
413 .expect("failed to make extra fxfs volume");
414 }
415
416 if self.data_spec.format.is_some() {
417 self.init_data_fxfs(FxfsType::FxBlob(fs, crypt_realm)).await;
418 } else {
419 fs.shutdown().await.expect("shutdown failed");
420 }
421 }
422
423 async fn build_fvm_as_volume_manager(&mut self, connector: Box<dyn BlockConnector>) {
424 let block_device = connector.connect_block().unwrap().into_proxy();
425 fasync::unblock(move || format_for_fvm(&block_device, FVM_SLICE_SIZE as usize))
426 .await
427 .unwrap();
428 let mut fvm_fs = Filesystem::from_boxed_config(connector, Box::new(Fvm::dynamic_child()));
429 let mut fvm = fvm_fs.serve_multi_volume().await.unwrap();
430
431 {
432 let blob_volume = fvm
433 .create_volume(
434 "blobfs",
435 CreateOptions {
436 type_guid: Some(BLOBFS_TYPE_GUID),
437 guid: Some(Uuid::new_v4().into_bytes()),
438 ..Default::default()
439 },
440 MountOptions {
441 uri: Some(String::from("#meta/blobfs.cm")),
442 ..Default::default()
443 },
444 )
445 .await
446 .expect("failed to make fvm blobfs volume");
447 let blob_creator =
448 connect_to_protocol_at_dir_svc::<fidl_fuchsia_fxfs::BlobCreatorMarker>(
449 blob_volume.exposed_dir(),
450 )
451 .expect("failed to connect to the Blob service");
452 self.blob_hash = Some(write_test_blob(blob_creator, &BLOB_CONTENTS).await);
453 }
454 fvm.shutdown_volume("blobfs").await.unwrap();
455
456 if self.volumes_spec.create_data_partition {
457 let data_label = if self.legacy_data_label { "minfs" } else { "data" };
458
459 let _crypt_service;
460 let crypt = if self.data_spec.format != Some("fxfs") && self.data_spec.zxcrypt {
461 let (crypt, stream) = fidl::endpoints::create_request_stream();
462 _crypt_service = fasync::Task::spawn(zxcrypt_crypt::run_crypt_service(
463 crypt_policy::Policy::Null,
464 stream,
465 ));
466 Some(crypt)
467 } else {
468 None
469 };
470 let uri = match (&self.data_spec.format, self.corrupt_data) {
471 (None, _) => None,
472 (_, true) => None,
473 (Some("fxfs"), false) => None,
474 (Some("minfs"), false) => Some(String::from("#meta/minfs.cm")),
475 (Some("f2fs"), false) => Some(String::from("#meta/f2fs.cm")),
476 (Some(format), _) => panic!("unsupported data volume format '{}'", format),
477 };
478
479 let data_volume = fvm
480 .create_volume(
481 data_label,
482 CreateOptions {
483 initial_size: Some(self.data_volume_size),
484 type_guid: Some(DATA_TYPE_GUID),
485 guid: Some(Uuid::new_v4().into_bytes()),
486 ..Default::default()
487 },
488 MountOptions { crypt, uri, ..Default::default() },
489 )
490 .await
491 .unwrap();
492
493 if self.corrupt_data {
494 let volume_proxy = connect_to_protocol_at_dir_svc::<
495 fidl_fuchsia_hardware_block_volume::VolumeMarker,
496 >(data_volume.exposed_dir())
497 .unwrap();
498 match self.data_spec.format {
499 Some("fxfs") => self.write_magic(volume_proxy, FXFS_MAGIC, 0).await,
500 Some("minfs") => self.write_magic(volume_proxy, MINFS_MAGIC, 0).await,
501 Some("f2fs") => self.write_magic(volume_proxy, F2FS_MAGIC, 1024).await,
502 _ => (),
503 }
504 } else if self.data_spec.format == Some("fxfs") {
505 let dir = fuchsia_fs::directory::clone(data_volume.exposed_dir()).unwrap();
506 self.init_data_fxfs(FxfsType::Fxfs(Box::new(DirBasedBlockConnector::new(
507 dir,
508 String::from("svc/fuchsia.hardware.block.volume.Volume"),
509 ))))
510 .await
511 } else if self.data_spec.format.is_some() {
512 self.write_test_data(data_volume.root()).await;
513 fvm.shutdown_volume(data_label).await.unwrap();
514 }
515 }
516
517 for volume in &self.extra_volumes {
518 fvm.create_volume(
519 volume,
520 CreateOptions {
521 type_guid: Some(DATA_TYPE_GUID),
522 guid: Some(Uuid::new_v4().into_bytes()),
523 ..Default::default()
524 },
525 MountOptions::default(),
526 )
527 .await
528 .expect("failed to make extra fvm volume");
529 }
530
531 fvm.shutdown().await.expect("fvm shutdown failed");
532 }
533
534 async fn init_data_fxfs(&self, fxfs: FxfsType) {
535 let mut fxblob = false;
536 let (mut fs, crypt_realm) = match fxfs {
537 FxfsType::Fxfs(connector) => {
538 let crypt_realm = create_hermetic_crypt_service(DATA_KEY, METADATA_KEY).await;
539 let mut fxfs =
540 Filesystem::from_boxed_config(connector, Box::new(Fxfs::dynamic_child()));
541 fxfs.format().await.expect("format failed");
542 (fxfs.serve_multi_volume().await.expect("serve_multi_volume failed"), crypt_realm)
543 }
544 FxfsType::FxBlob(fs, crypt_realm) => {
545 fxblob = true;
546 (fs, crypt_realm)
547 }
548 };
549
550 let vol = {
551 let vol = fs
552 .create_volume("unencrypted", CreateOptions::default(), MountOptions::default())
553 .await
554 .expect("create_volume failed");
555 let keys_dir = fuchsia_fs::directory::create_directory(
556 vol.root(),
557 "keys",
558 fio::PERM_READABLE | fio::PERM_WRITABLE,
559 )
560 .await
561 .unwrap();
562 let keys_file = fuchsia_fs::directory::open_file(
563 &keys_dir,
564 "fxfs-data",
565 fio::Flags::FLAG_MAYBE_CREATE
566 | fio::Flags::PROTOCOL_FILE
567 | fio::PERM_READABLE
568 | fio::PERM_WRITABLE,
569 )
570 .await
571 .unwrap();
572 let mut key_bag = KEY_BAG_CONTENTS.as_bytes();
573 if self.corrupt_data && fxblob {
574 key_bag = &BLOB_CONTENTS;
575 }
576 fuchsia_fs::file::write(&keys_file, key_bag).await.unwrap();
577 fuchsia_fs::file::close(keys_file).await.unwrap();
578 fuchsia_fs::directory::close(keys_dir).await.unwrap();
579
580 let crypt_service = Some(
581 crypt_realm
582 .root
583 .connect_to_protocol_at_exposed_dir::<CryptMarker>()
584 .expect("Unable to connect to Crypt service")
585 .into_channel()
586 .unwrap()
587 .into_zx_channel()
588 .into(),
589 );
590 fs.create_volume(
591 "data",
592 CreateOptions::default(),
593 MountOptions { crypt: crypt_service, ..MountOptions::default() },
594 )
595 .await
596 .expect("create_volume failed")
597 };
598 self.write_test_data(&vol.root()).await;
599 fs.shutdown().await.expect("shutdown failed");
600 }
601
602 async fn write_test_data(&self, root: &fio::DirectoryProxy) {
610 fuchsia_fs::directory::open_file(
611 root,
612 ".testdata",
613 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE,
614 )
615 .await
616 .unwrap();
617
618 let ssh_dir = fuchsia_fs::directory::create_directory(
619 root,
620 "ssh",
621 fio::PERM_READABLE | fio::PERM_WRITABLE,
622 )
623 .await
624 .unwrap();
625 let authorized_keys = fuchsia_fs::directory::open_file(
626 &ssh_dir,
627 "authorized_keys",
628 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
629 )
630 .await
631 .unwrap();
632 fuchsia_fs::file::write(&authorized_keys, "public key!").await.unwrap();
633 fuchsia_fs::directory::create_directory(&ssh_dir, "config", fio::PERM_READABLE)
634 .await
635 .unwrap();
636
637 fuchsia_fs::directory::create_directory(&root, "problems", fio::PERM_READABLE)
638 .await
639 .unwrap();
640
641 if let Some(content) = &self.fs_switch {
642 let fs_switch = fuchsia_fs::directory::open_file(
643 &root,
644 "fs_switch",
645 fio::Flags::FLAG_MAYBE_CREATE | fio::PERM_READABLE | fio::PERM_WRITABLE,
646 )
647 .await
648 .unwrap();
649 fuchsia_fs::file::write(&fs_switch, content).await.unwrap();
650 }
651 }
652
653 async fn write_magic<const N: usize>(
654 &self,
655 volume_proxy: fidl_fuchsia_hardware_block_volume::VolumeProxy,
656 value: [u8; N],
657 offset: u64,
658 ) {
659 let client = block_client::RemoteBlockClient::new(volume_proxy)
660 .await
661 .expect("Failed to create client");
662 let block_size = client.block_size() as usize;
663 assert!(value.len() <= block_size);
664 let mut data = vec![0xffu8; block_size];
665 data[..value.len()].copy_from_slice(&value);
666 let buffer = block_client::BufferSlice::Memory(&data[..]);
667 client.write_at(buffer, offset).await.expect("write failed");
668 }
669
670 pub(crate) async fn build_as_zbi_ramdisk(self) -> zx::Vmo {
673 const ZBI_TYPE_STORAGE_RAMDISK: u32 = 0x4b534452;
676 const ZBI_FLAGS_VERSION: u32 = 0x00010000;
677 const ZBI_ITEM_MAGIC: u32 = 0xb5781729;
678 const ZBI_FLAGS_STORAGE_COMPRESSED: u32 = 0x00000001;
679
680 #[repr(C)]
681 #[derive(IntoBytes, Immutable)]
682 struct ZbiHeader {
683 type_: u32,
684 length: u32,
685 extra: u32,
686 flags: u32,
687 _reserved0: u32,
688 _reserved1: u32,
689 magic: u32,
690 _crc32: u32,
691 }
692
693 let (ramdisk_vmo, _) = self.build().await;
694 let extra = ramdisk_vmo.get_size().unwrap() as u32;
695 let mut decompressed_buf = vec![0u8; extra as usize];
696 ramdisk_vmo.read(&mut decompressed_buf, 0).unwrap();
697 let compressed_buf = zstd::encode_all(decompressed_buf.as_slice(), 0).unwrap();
698 let length = compressed_buf.len() as u32;
699
700 let header = ZbiHeader {
701 type_: ZBI_TYPE_STORAGE_RAMDISK,
702 length,
703 extra,
704 flags: ZBI_FLAGS_VERSION | ZBI_FLAGS_STORAGE_COMPRESSED,
705 _reserved0: 0,
706 _reserved1: 0,
707 magic: ZBI_ITEM_MAGIC,
708 _crc32: 0,
709 };
710
711 let header_size = std::mem::size_of::<ZbiHeader>() as u64;
712 let zbi_vmo = zx::Vmo::create(header_size + length as u64).unwrap();
713 zbi_vmo.write(header.as_bytes(), 0).unwrap();
714 zbi_vmo.write(&compressed_buf, header_size).unwrap();
715
716 zbi_vmo
717 }
718}