1use crate::errors::FxfsError;
6use crate::lsm_tree::Query;
7use crate::lsm_tree::types::{ItemRef, LayerIterator};
8use crate::object_handle::ObjectHandle;
9use crate::object_store::object_record::{AttributeKey, ObjectKey, ObjectKeyData, ObjectValue};
10use crate::object_store::{AttributeId, DataObjectHandle, HandleOwner, StoreObjectHandle};
11use crate::serialized_types::{Versioned, VersionedLatest};
12use anyhow::{Context, Error};
13use fprint::TypeFingerprint;
14use fuchsia_merkle::{Hash, LeafHashCollector, MerkleVerifier};
15use serde::{Deserialize, Serialize};
16
17#[derive(Serialize, Deserialize, Debug)]
18pub struct BlobMetadataUnversioned {
19 pub hashes: Vec<[u8; 32]>,
20 pub chunk_size: u64,
21 pub compressed_offsets: Vec<u64>,
22 pub uncompressed_size: u64,
23}
24
25pub type BlobMetadata = BlobMetadataV53;
26pub type BlobFormat = BlobFormatV53;
27pub type MerkleLeaves = Vec<[u8; 32]>;
28
29impl BlobMetadata {
30 pub async fn read_from<S: HandleOwner>(
33 blob_object: &StoreObjectHandle<S>,
34 ) -> Result<Self, Error> {
35 let store = blob_object.store();
36 let layer_set = store.tree().layer_set();
37 let mut merger = layer_set.merger();
38 static_assertions::const_assert!(
44 AttributeId::BLOB_MERKLE.raw() < AttributeId::BLOB_METADATA.raw()
45 );
46 let key = ObjectKey::attribute(
47 blob_object.object_id(),
48 AttributeId::BLOB_MERKLE,
49 AttributeKey::Attribute,
50 );
51 let iter = merger.query(Query::FullRange(&key)).await?;
52 match iter.get() {
53 Some(ItemRef {
54 key:
55 ObjectKey {
56 object_id,
57 data:
58 ObjectKeyData::Attribute(AttributeId::BLOB_MERKLE, AttributeKey::Attribute),
59 },
60 value,
61 ..
62 }) if *object_id == blob_object.object_id() => match value {
63 ObjectValue::Attribute { .. } => {
64 let serialized_metadata = blob_object.read_attr_from_iter(iter).await?;
65 let old_metadata: BlobMetadataUnversioned =
66 bincode::deserialize_from(&*serialized_metadata)?;
67 Ok(Self::from(old_metadata))
68 }
69 _ => Err(FxfsError::Inconsistent.into()),
70 },
71 Some(ItemRef {
72 key:
73 ObjectKey {
74 object_id,
75 data:
76 ObjectKeyData::Attribute(
77 AttributeId::BLOB_METADATA,
78 AttributeKey::Attribute,
79 ),
80 },
81 value,
82 ..
83 }) if *object_id == blob_object.object_id() => match value {
84 ObjectValue::Attribute { .. } => {
85 let serialized_metadata = blob_object.read_attr_from_iter(iter).await?;
86 Ok(Self::deserialize_with_version(&mut &*serialized_metadata)?.0)
87 }
88 _ => Err(FxfsError::Inconsistent.into()),
89 },
90 Some(ItemRef {
91 key:
92 ObjectKey {
93 object_id,
94 data:
95 ObjectKeyData::Attribute(
96 AttributeId::FSVERITY_MERKLE,
97 AttributeKey::Attribute,
98 ),
99 },
100 ..
101 }) if *object_id == blob_object.object_id() => {
102 Err(FxfsError::Inconsistent.into())
108 }
109 _ => Ok(Self::empty()),
111 }
112 }
113
114 pub async fn write_to<S: HandleOwner>(
117 &self,
118 blob_object: &DataObjectHandle<S>,
119 ) -> Result<(), Error> {
120 if self.is_empty() {
122 return Ok(());
123 }
124 let mut buf = Vec::new();
125 self.serialize_with_version(&mut buf)?;
126 blob_object
127 .write_attr(AttributeId::BLOB_METADATA, &buf)
128 .await
129 .context("Failed to write blob metadata attribute.")
130 }
131
132 pub fn serialized_size(&self) -> Result<usize, Error> {
135 if self.is_empty() {
136 return Ok(0);
137 }
138 struct CountingWriter(usize);
139 impl std::io::Write for CountingWriter {
140 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
141 self.0 += buf.len();
142 Ok(buf.len())
143 }
144 fn flush(&mut self) -> std::io::Result<()> {
145 Ok(())
146 }
147 }
148 let mut writer = CountingWriter(0);
149 self.serialize_with_version(&mut writer)?;
150 Ok(writer.0)
151 }
152
153 pub fn into_merkle_verifier(self, root: Hash) -> Result<MerkleVerifier, Error> {
155 let hashes = if self.merkle_leaves.is_empty() {
156 Box::new([root])
157 } else {
158 self.merkle_leaves.into_iter().map(Into::into).collect::<Box<[Hash]>>()
163 };
164 Ok(MerkleVerifier::new(root, hashes)?)
165 }
166
167 pub fn empty() -> Self {
170 Self { merkle_leaves: Vec::new(), format: BlobFormatV53::Uncompressed }
173 }
174
175 fn is_empty(&self) -> bool {
176 *self == Self::empty()
177 }
178}
179
180#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, TypeFingerprint)]
181pub struct BlobMetadataV53 {
182 #[serde(with = "crate::zerocopy_serialization")]
183 pub merkle_leaves: MerkleLeaves,
184 pub format: BlobFormatV53,
185}
186
187impl Versioned for BlobMetadataV53 {
188 fn max_serialized_size() -> Option<u64> {
189 None
191 }
192}
193
194impl From<BlobMetadataUnversioned> for BlobMetadataV53 {
195 fn from(old: BlobMetadataUnversioned) -> Self {
196 if old.compressed_offsets.is_empty() {
197 Self { merkle_leaves: old.hashes, format: BlobFormat::Uncompressed }
198 } else {
199 Self {
200 merkle_leaves: old.hashes,
201 format: BlobFormat::ChunkedZstd {
202 uncompressed_size: old.uncompressed_size,
203 chunk_size: old.chunk_size,
204 compressed_offsets: old.compressed_offsets,
205 },
206 }
207 }
208 }
209}
210
211#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, TypeFingerprint)]
212pub enum BlobFormatV53 {
213 Uncompressed,
214 ChunkedZstd { uncompressed_size: u64, chunk_size: u64, compressed_offsets: Vec<u64> },
215 ChunkedLz4 { uncompressed_size: u64, chunk_size: u64, compressed_offsets: Vec<u64> },
216}
217
218#[derive(Default)]
219pub struct BlobMetadataLeafHashCollector(MerkleLeaves);
220
221impl BlobMetadataLeafHashCollector {
222 pub fn new() -> Self {
223 Self(Vec::new())
224 }
225}
226
227impl LeafHashCollector for BlobMetadataLeafHashCollector {
228 type Output = (Hash, MerkleLeaves);
229
230 fn add_leaf_hash(&mut self, hash: Hash) {
231 self.0.push(hash.into())
232 }
233
234 fn complete(mut self, root: Hash) -> Self::Output {
235 if self.0.len() == 1 {
237 debug_assert!(*root == self.0[0]);
238 self.0 = Vec::new();
239 }
240 (root, self.0)
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::BlobMetadata;
247 use crate::blob_metadata::{
248 BlobFormat, BlobMetadataLeafHashCollector, BlobMetadataUnversioned,
249 };
250 use crate::filesystem::{FxFilesystem, OpenFxFilesystem};
251 use crate::object_store::transaction::{LockKey, Options, lock_keys};
252 use crate::object_store::{
253 AttributeId, DataObjectHandle, Directory, HandleOptions, ObjectStore,
254 };
255 use assert_matches::assert_matches;
256 use fuchsia_merkle::MerkleRootBuilder;
257 use storage_device::DeviceHolder;
258 use storage_device::fake_device::FakeDevice;
259
260 const TEST_DEVICE_BLOCK_SIZE: u32 = 512;
261 const TEST_DEVICE_BLOCK_COUNT: u64 = 16 * 1024;
262 const TEST_OBJECT_NAME: &str = "foo";
263
264 async fn test_filesystem() -> OpenFxFilesystem {
265 let device =
266 DeviceHolder::new(FakeDevice::new(TEST_DEVICE_BLOCK_COUNT, TEST_DEVICE_BLOCK_SIZE));
267 FxFilesystem::new_empty(device).await.expect("new_empty failed")
268 }
269
270 async fn test_filesystem_and_empty_object() -> (OpenFxFilesystem, DataObjectHandle<ObjectStore>)
271 {
272 let fs = test_filesystem().await;
273 let store = fs.root_store();
274
275 let mut transaction = fs
276 .clone()
277 .new_transaction(
278 lock_keys![LockKey::object(
279 store.store_object_id(),
280 store.root_directory_object_id()
281 )],
282 Options::default(),
283 )
284 .await
285 .expect("new_transaction failed");
286
287 let object =
288 ObjectStore::create_object(&store, &mut transaction, HandleOptions::default(), None)
289 .await
290 .expect("create_object failed");
291
292 let root_directory =
293 Directory::open(&store, store.root_directory_object_id()).await.expect("open failed");
294 root_directory
295 .add_child_file(&mut transaction, TEST_OBJECT_NAME, &object)
296 .await
297 .expect("add_child_file failed");
298
299 transaction.commit().await.expect("commit failed");
300
301 (fs, object)
302 }
303
304 #[fuchsia::test(threads = 3)]
305 async fn test_write_read_zstd() {
306 let (fs, object) = test_filesystem_and_empty_object().await;
307
308 let metadata = BlobMetadata {
309 merkle_leaves: vec![[1; 32], [2; 32], [3; 32], [4; 32]],
310 format: BlobFormat::ChunkedZstd {
311 uncompressed_size: 128 * 1024,
312 chunk_size: 32 * 1024,
313 compressed_offsets: vec![0, 100, 200, 400],
314 },
315 };
316 metadata.write_to(&object).await.expect("failed to write attribute");
317 let read_metadata =
318 BlobMetadata::read_from(&object).await.expect("failed to read attribute");
319 assert_eq!(read_metadata, metadata);
320
321 fs.close().await.expect("close failed");
322 }
323
324 #[fuchsia::test(threads = 3)]
325 async fn test_write_read_lz4() {
326 let (fs, object) = test_filesystem_and_empty_object().await;
327
328 let metadata = BlobMetadata {
329 merkle_leaves: vec![[1; 32], [2; 32], [3; 32], [4; 32]],
330 format: BlobFormat::ChunkedLz4 {
331 uncompressed_size: 128 * 1024,
332 chunk_size: 32 * 1024,
333 compressed_offsets: vec![0, 100, 200, 400],
334 },
335 };
336 metadata.write_to(&object).await.expect("failed to write attribute");
337 let read_metadata =
338 BlobMetadata::read_from(&object).await.expect("failed to read attribute");
339 assert_eq!(read_metadata, metadata);
340
341 fs.close().await.expect("close failed");
342 }
343
344 #[fuchsia::test(threads = 3)]
345 async fn test_empty_attribute_is_not_written() {
346 let (fs, object) = test_filesystem_and_empty_object().await;
347
348 BlobMetadata::empty().write_to(&object).await.expect("failed to write attribute");
349 let result = object
350 .read_attr(AttributeId::BLOB_METADATA)
351 .await
352 .expect("reading the attribute failed");
353 assert_eq!(result, None);
354
355 fs.close().await.expect("close failed");
356 }
357
358 #[fuchsia::test(threads = 3)]
359 async fn test_read_corrupt_attribute_fails() {
360 let (fs, object) = test_filesystem_and_empty_object().await;
361
362 object
363 .write_attr(AttributeId::BLOB_METADATA, b"garbage")
364 .await
365 .expect("failed to write attribute");
366 BlobMetadata::read_from(&object).await.expect_err("reading the metadata should fail");
367
368 fs.close().await.expect("close failed");
369 }
370
371 #[fuchsia::test(threads = 3)]
372 async fn test_read_unversioned_attribute() {
373 let (fs, object) = test_filesystem_and_empty_object().await;
374
375 let unversioned_metadata = BlobMetadataUnversioned {
376 hashes: vec![[1; 32], [2; 32]],
377 chunk_size: 32 * 1024,
378 compressed_offsets: vec![0],
379 uncompressed_size: 15 * 1024,
380 };
381 let mut buf = Vec::new();
382 bincode::serialize_into(&mut buf, &unversioned_metadata)
383 .expect("failed to serialize metadata");
384 object.write_attr(AttributeId::BLOB_MERKLE, &buf).await.expect("failed to write attribute");
385 let metadata = BlobMetadata::read_from(&object).await.expect("failed to read attribute");
386 assert_eq!(metadata, BlobMetadata::from(unversioned_metadata));
387
388 fs.close().await.expect("close failed");
389 }
390
391 #[fuchsia::test(threads = 3)]
392 async fn test_read_corrupt_unversioned_attribute_fails() {
393 let (fs, object) = test_filesystem_and_empty_object().await;
394
395 object
396 .write_attr(AttributeId::BLOB_MERKLE, b"garbage")
397 .await
398 .expect("failed to write attribute");
399 BlobMetadata::read_from(&object).await.expect_err("reading the metadata should fail");
400
401 fs.close().await.expect("close failed");
402 }
403
404 #[fuchsia::test(threads = 3)]
405 async fn test_fs_verity_hides_blob_metadata() {
406 let (fs, object) = test_filesystem_and_empty_object().await;
407
408 let metadata = BlobMetadata {
409 merkle_leaves: vec![[1; 32], [2; 32]],
410 format: BlobFormat::Uncompressed,
411 };
412 metadata.write_to(&object).await.expect("failed to write attribute");
413 object
414 .write_attr(AttributeId::FSVERITY_MERKLE, b"fs-verify")
415 .await
416 .expect("failed to write fs-verity attribute");
417 BlobMetadata::read_from(&object).await.expect_err("fs-verity should have been found");
418
419 fs.close().await.expect("close failed");
420 }
421
422 #[fuchsia::test]
423 async fn test_serialized_size() {
424 assert_matches!(BlobMetadata::empty().serialized_size(), Ok(0));
425 assert_matches!(
426 BlobMetadata {
427 merkle_leaves: vec![[54; 32], [55; 32]],
428 format: BlobFormat::Uncompressed,
429 }
430 .serialized_size(),
431 Ok(70)
436 );
437 assert_matches!(
438 BlobMetadata {
439 merkle_leaves: vec![[54; 32], [55; 32]],
440 format: BlobFormat::ChunkedZstd {
441 uncompressed_size: 128 * 1024,
442 chunk_size: 32 * 1024,
443 compressed_offsets: vec![0, 100, 200, 400],
444 },
445 }
446 .serialized_size(),
447 Ok(85)
456 );
457 }
458
459 #[fuchsia::test]
460 fn test_leaf_hash_collector_with_only_root() {
461 let data = vec![3; 4096];
462 let (_root, leaves) =
463 MerkleRootBuilder::new(BlobMetadataLeafHashCollector::new()).complete(&data);
464 assert!(leaves.is_empty());
465 }
466
467 #[fuchsia::test]
468 fn test_leaf_hash_collector_with_leaves() {
469 let data = vec![3; 12 * 1024];
470 let (_root, leaves) =
471 MerkleRootBuilder::new(BlobMetadataLeafHashCollector::new()).complete(&data);
472 assert_eq!(leaves.len(), 2);
473 }
474
475 #[fuchsia::test]
476 fn test_into_merkle_verifier_with_only_root() {
477 let data = vec![3; 4096];
478 let (root, leaves) =
479 MerkleRootBuilder::new(BlobMetadataLeafHashCollector::new()).complete(&data);
480 let metadata = BlobMetadata { merkle_leaves: leaves, format: BlobFormat::Uncompressed };
481 let verifier =
482 metadata.into_merkle_verifier(root).expect("failed to create merkle verifier");
483 verifier.verify(0, &data).expect("failed to verify data");
484 }
485
486 #[fuchsia::test]
487 fn test_into_merkle_verifier_with_leaves() {
488 let data = vec![3; 12 * 1024];
489 let (root, leaves) =
490 MerkleRootBuilder::new(BlobMetadataLeafHashCollector::new()).complete(&data);
491 let metadata = BlobMetadata { merkle_leaves: leaves, format: BlobFormat::Uncompressed };
492 let verifier =
493 metadata.into_merkle_verifier(root).expect("failed to create merkle verifier");
494 verifier.verify(0, &data).expect("failed to verify data");
495 }
496
497 #[fuchsia::test]
498 fn test_convert_unversioned_to_versioned() {
499 assert_eq!(
500 BlobMetadata::from(BlobMetadataUnversioned {
501 hashes: vec![[1; 32], [2; 32]],
502 chunk_size: 0,
503 compressed_offsets: vec![],
504 uncompressed_size: 15 * 1024,
505 }),
506 BlobMetadata {
507 merkle_leaves: vec![[1; 32], [2; 32]],
508 format: BlobFormat::Uncompressed,
509 }
510 );
511
512 assert_eq!(
513 BlobMetadata::from(BlobMetadataUnversioned {
514 hashes: vec![[1; 32], [2; 32], [3; 32], [4; 32]],
515 chunk_size: 32 * 1024,
516 compressed_offsets: vec![0, 100],
517 uncompressed_size: 33 * 1024,
518 }),
519 BlobMetadata {
520 merkle_leaves: vec![[1; 32], [2; 32], [3; 32], [4; 32]],
521 format: BlobFormat::ChunkedZstd {
522 uncompressed_size: 33 * 1024,
523 chunk_size: 32 * 1024,
524 compressed_offsets: vec![0, 100]
525 },
526 }
527 );
528 }
529
530 #[fuchsia::test]
531 fn test_merkle_serialization() {}
532}