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