1use crate::compression::{ChunkedArchive, ChunkedArchiveOptions, ChunkedDecompressor};
16use crate::format::{SerializedType1Blob, SerializedType3Blob};
17use serde::{Deserialize, Serialize};
18use static_assertions::assert_eq_size;
19use thiserror::Error;
20use zerocopy::{IntoBytes, Ref};
21
22pub mod compression;
23pub use compression::DataBuffer;
24mod format;
25
26assert_eq_size!(usize, u64);
28
29pub fn generate(delivery_type: DeliveryBlobType, data: &[u8]) -> Vec<u8> {
31 match delivery_type {
32 DeliveryBlobType::Type1 => Type1Blob::generate(data, CompressionMode::Attempt),
33 DeliveryBlobType::Type2 => Type2Blob::generate(data, CompressionMode::Attempt),
34 DeliveryBlobType::Type3 => Type3Blob::generate(data, CompressionMode::Attempt),
35 _ => panic!("Unsupported delivery blob type: {:?}", delivery_type),
36 }
37}
38
39pub fn generate_to(
42 delivery_type: DeliveryBlobType,
43 data: &[u8],
44 writer: impl std::io::Write,
45) -> Result<(), std::io::Error> {
46 match delivery_type {
47 DeliveryBlobType::Type1 => Type1Blob::generate_to(data, CompressionMode::Attempt, writer),
48 DeliveryBlobType::Type2 => Type2Blob::generate_to(data, CompressionMode::Attempt, writer),
49 DeliveryBlobType::Type3 => Type3Blob::generate_to(data, CompressionMode::Attempt, writer),
50 _ => panic!("Unsupported delivery blob type: {:?}", delivery_type),
51 }
52}
53
54pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
56 DeliveryBlob::decompressed_size(delivery_blob)
57}
58
59pub fn decompressed_size_from_reader(
61 mut reader: impl std::io::Read,
62) -> Result<u64, DecompressError> {
63 let mut buf = vec![];
64 loop {
65 let already_read = buf.len();
66 let new_size = already_read + 4096;
67 buf.resize(new_size, 0);
68 let new_size = already_read + reader.read(&mut buf[already_read..new_size])?;
69 if new_size == already_read {
70 return Err(DecompressError::NeedMoreData);
71 }
72 buf.truncate(new_size);
73 match decompressed_size(&buf) {
74 Ok(size) => {
75 return Ok(size);
76 }
77 Err(DecompressError::NeedMoreData) => {}
78 Err(e) => {
79 return Err(e);
80 }
81 }
82 }
83}
84
85pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
87 DeliveryBlob::decompress(delivery_blob)
88}
89
90pub fn decompress_to(
93 delivery_blob: &[u8],
94 writer: impl std::io::Write,
95) -> Result<(), DecompressError> {
96 DeliveryBlob::decompress_to(delivery_blob, writer)
97}
98
99pub fn calculate_digest(delivery_blob: &[u8]) -> Result<fuchsia_merkle::Hash, DecompressError> {
102 let mut writer = fuchsia_merkle::BufferedMerkleRootBuilder::default();
103 let () = DeliveryBlob::decompress_to(delivery_blob, &mut writer)?;
104 Ok(writer.complete())
105}
106
107#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
108pub enum DeliveryBlobError {
109 #[error("Invalid or unsupported delivery blob type.")]
110 InvalidType,
111
112 #[error("Delivery blob header has incorrect magic.")]
113 BadMagic,
114
115 #[error("Integrity/checksum or other validity checks failed.")]
116 IntegrityError,
117}
118
119#[derive(Debug, Error)]
120pub enum DecompressError {
121 #[error("DeliveryBlob error")]
122 DeliveryBlob(#[from] DeliveryBlobError),
123
124 #[error("ChunkedArchive error")]
125 ChunkedArchive(#[from] compression::ChunkedArchiveError),
126
127 #[error("Need more data")]
128 NeedMoreData,
129
130 #[error("io error")]
131 IoError(#[from] std::io::Error),
132}
133
134#[cfg(target_os = "fuchsia")]
135impl From<DeliveryBlobError> for zx::Status {
136 fn from(value: DeliveryBlobError) -> Self {
137 match value {
138 DeliveryBlobError::InvalidType => zx::Status::NOT_SUPPORTED,
140 DeliveryBlobError::BadMagic | DeliveryBlobError::IntegrityError => {
142 zx::Status::IO_DATA_INTEGRITY
143 }
144 }
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub struct DeliveryBlobHeader {
151 pub delivery_type: DeliveryBlobType,
152 pub header_length: u32,
153}
154
155impl DeliveryBlobHeader {
156 pub fn parse(data: &[u8]) -> Result<Option<DeliveryBlobHeader>, DeliveryBlobError> {
160 let Ok((serialized_header, _metadata_and_payload)) =
161 Ref::<_, format::SerializedHeader>::from_prefix(data)
162 else {
163 return Ok(None);
164 };
165 serialized_header.decode().map(Some)
166 }
167}
168
169#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
174#[repr(u32)]
175pub enum DeliveryBlobType {
176 Reserved = 0,
178 Type1 = 1,
180 Type2 = 2,
182 Type3 = 3,
185}
186
187impl TryFrom<u32> for DeliveryBlobType {
188 type Error = DeliveryBlobError;
189 fn try_from(value: u32) -> Result<Self, Self::Error> {
190 match value {
191 value if value == DeliveryBlobType::Reserved as u32 => Ok(DeliveryBlobType::Reserved),
192 value if value == DeliveryBlobType::Type1 as u32 => Ok(DeliveryBlobType::Type1),
193 value if value == DeliveryBlobType::Type2 as u32 => Ok(DeliveryBlobType::Type2),
194 value if value == DeliveryBlobType::Type3 as u32 => Ok(DeliveryBlobType::Type3),
195 _ => Err(DeliveryBlobError::InvalidType),
196 }
197 }
198}
199
200impl From<DeliveryBlobType> for u32 {
201 fn from(value: DeliveryBlobType) -> Self {
202 value as u32
203 }
204}
205
206#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub enum CompressionMode {
209 Never,
211 Attempt,
213 Always,
215}
216
217#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219pub struct DeliveryBlob {
220 pub header: DeliveryBlobHeader,
221 pub payload_length: usize,
222 pub is_compressed: bool,
223}
224
225impl DeliveryBlob {
226 pub fn parse(
232 data: &[u8],
233 allow_type3: bool,
234 ) -> Result<Option<(DeliveryBlob, &[u8])>, DeliveryBlobError> {
235 let Some(header) = DeliveryBlobHeader::parse(data)? else {
236 return Ok(None);
237 };
238 match header.delivery_type {
239 DeliveryBlobType::Type1 | DeliveryBlobType::Type2 => {
240 let Ok((serialized_header, payload)) =
241 Ref::<_, format::SerializedType1Blob>::from_prefix(data)
242 else {
243 return Ok(None);
244 };
245 serialized_header.decode().map(|metadata| Some((metadata, payload)))
246 }
247 DeliveryBlobType::Type3 => {
248 if !allow_type3 {
249 return Err(DeliveryBlobError::InvalidType);
250 }
251 let Ok((serialized_header, payload)) =
252 Ref::<_, format::SerializedType3Blob>::from_prefix(data)
253 else {
254 return Ok(None);
255 };
256 serialized_header.decode().map(|metadata| Some((metadata.into(), payload)))
257 }
258 _ => Err(DeliveryBlobError::InvalidType),
259 }
260 }
261
262 pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
264 let (header, payload) =
265 Self::parse(delivery_blob, true)?.ok_or(DecompressError::NeedMoreData)?;
266 if !header.is_compressed {
267 return Ok(header.payload_length as u64);
268 }
269
270 let (decoded_archive, _chunk_data) =
271 compression::decode_archive(payload, header.payload_length)?
272 .ok_or(DecompressError::NeedMoreData)?;
273 Ok(decoded_archive.decompressed_size() as u64)
274 }
275
276 pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
278 let mut decompressed = vec![];
279 decompressed.reserve(Self::decompressed_size(delivery_blob)? as usize);
280 Self::decompress_to(delivery_blob, &mut decompressed)?;
281 Ok(decompressed)
282 }
283
284 pub fn decompress_to(
286 delivery_blob: &[u8],
287 mut writer: impl std::io::Write,
288 ) -> Result<(), DecompressError> {
289 let (header, payload) =
290 Self::parse(delivery_blob, true)?.ok_or(DecompressError::NeedMoreData)?;
291 if !header.is_compressed {
292 return Ok(writer.write_all(payload)?);
293 }
294
295 let (decoded_archive, chunk_data) =
296 compression::decode_archive(payload, header.payload_length)?
297 .ok_or(DecompressError::NeedMoreData)?;
298 let mut decompressor = ChunkedDecompressor::new(decoded_archive)?;
299 let mut result = Ok(());
300 let mut chunk_callback = |chunk: &[u8]| {
301 if let Err(e) = writer.write_all(chunk) {
302 result = Err(e.into());
303 }
304 };
305 decompressor.update(chunk_data, &mut chunk_callback)?;
306 result
307 }
308}
309
310#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316pub struct Type1Blob {
317 pub header: DeliveryBlobHeader,
318 pub payload_length: usize,
319 pub is_compressed: bool,
320}
321
322impl From<DeliveryBlob> for Type1Blob {
323 fn from(blob: DeliveryBlob) -> Self {
324 Self {
325 header: blob.header,
326 payload_length: blob.payload_length,
327 is_compressed: blob.is_compressed,
328 }
329 }
330}
331
332impl From<Type1Blob> for DeliveryBlob {
333 fn from(blob: Type1Blob) -> Self {
334 Self {
335 header: blob.header,
336 payload_length: blob.payload_length,
337 is_compressed: blob.is_compressed,
338 }
339 }
340}
341
342impl Type1Blob {
343 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
344 delivery_type: DeliveryBlobType::Type1,
345 header_length: std::mem::size_of::<SerializedType1Blob>() as u32,
346 };
347
348 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions = ChunkedArchiveOptions::V2 {
349 chunk_alignment: fuchsia_merkle::BLOCK_SIZE,
350 minimum_chunk_size: 32 * 1024,
351 compression_level: 14,
352 };
353
354 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
356 let mut delivery_blob: Vec<u8> = vec![];
357 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
358 delivery_blob
359 }
360
361 pub fn generate_to(
364 data: &[u8],
365 mode: CompressionMode,
366 writer: impl std::io::Write,
367 ) -> Result<(), std::io::Error> {
368 generate_blob_to(Self::HEADER, Self::CHUNKED_ARCHIVE_OPTIONS, data, mode, writer)
369 }
370
371 pub fn parse(data: &[u8]) -> Result<Option<(Type1Blob, &[u8])>, DeliveryBlobError> {
374 match DeliveryBlob::parse(data, true)? {
375 Some((blob, payload)) if blob.header.delivery_type == DeliveryBlobType::Type1 => {
376 Ok(Some((blob.into(), payload)))
377 }
378 Some(_) => Err(DeliveryBlobError::InvalidType),
379 None => Ok(None),
380 }
381 }
382}
383
384#[derive(Clone, Copy, Debug, PartialEq, Eq)]
390pub struct Type2Blob {
391 pub header: DeliveryBlobHeader,
392 pub payload_length: usize,
393 pub is_compressed: bool,
394}
395
396impl From<DeliveryBlob> for Type2Blob {
397 fn from(blob: DeliveryBlob) -> Self {
398 Self {
399 header: blob.header,
400 payload_length: blob.payload_length,
401 is_compressed: blob.is_compressed,
402 }
403 }
404}
405
406impl From<Type2Blob> for DeliveryBlob {
407 fn from(blob: Type2Blob) -> Self {
408 Self {
409 header: blob.header,
410 payload_length: blob.payload_length,
411 is_compressed: blob.is_compressed,
412 }
413 }
414}
415
416impl Type2Blob {
417 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
418 delivery_type: DeliveryBlobType::Type2,
419 header_length: std::mem::size_of::<SerializedType1Blob>() as u32,
420 };
421
422 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions = ChunkedArchiveOptions::V2 {
423 chunk_alignment: fuchsia_merkle::BLOCK_SIZE,
424 minimum_chunk_size: 128 * 1024,
425 compression_level: 21,
426 };
427
428 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
430 let mut delivery_blob: Vec<u8> = vec![];
431 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
432 delivery_blob
433 }
434
435 pub fn generate_to(
438 data: &[u8],
439 mode: CompressionMode,
440 writer: impl std::io::Write,
441 ) -> Result<(), std::io::Error> {
442 generate_blob_to(Self::HEADER, Self::CHUNKED_ARCHIVE_OPTIONS, data, mode, writer)
443 }
444
445 pub fn parse(data: &[u8]) -> Result<Option<(Type2Blob, &[u8])>, DeliveryBlobError> {
448 match DeliveryBlob::parse(data, true)? {
449 Some((blob, payload)) if blob.header.delivery_type == DeliveryBlobType::Type2 => {
450 Ok(Some((blob.into(), payload)))
451 }
452 Some(_) => Err(DeliveryBlobError::InvalidType),
453 None => Ok(None),
454 }
455 }
456}
457
458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
466pub struct Type3Blob {
467 pub header: DeliveryBlobHeader,
468 pub payload_length: usize,
469 pub is_compressed: bool,
470}
471
472impl From<DeliveryBlob> for Type3Blob {
473 fn from(blob: DeliveryBlob) -> Self {
474 Self {
475 header: blob.header,
476 payload_length: blob.payload_length,
477 is_compressed: blob.is_compressed,
478 }
479 }
480}
481
482impl From<Type3Blob> for DeliveryBlob {
483 fn from(blob: Type3Blob) -> Self {
484 Self {
485 header: blob.header,
486 payload_length: blob.payload_length,
487 is_compressed: blob.is_compressed,
488 }
489 }
490}
491
492impl Type3Blob {
493 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
494 delivery_type: DeliveryBlobType::Type3,
495 header_length: std::mem::size_of::<SerializedType3Blob>() as u32,
496 };
497
498 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions =
499 ChunkedArchiveOptions::V3 { compression_algorithm: compression::CompressionAlgorithm::Lz4 };
500
501 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
503 let mut delivery_blob: Vec<u8> = vec![];
504 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
505 delivery_blob
506 }
507
508 pub fn generate_to(
511 data: &[u8],
512 mode: CompressionMode,
513 mut writer: impl std::io::Write,
514 ) -> Result<(), std::io::Error> {
515 let compressed = match mode {
516 CompressionMode::Attempt | CompressionMode::Always => {
517 let compressed = ChunkedArchive::new(data, Self::CHUNKED_ARCHIVE_OPTIONS)
518 .expect("failed to compress data");
519 if mode == CompressionMode::Always || compressed.serialized_size() <= data.len() {
520 Some(compressed)
521 } else {
522 None
523 }
524 }
525 CompressionMode::Never => None,
526 };
527
528 let payload_length =
529 compressed.as_ref().map(|archive| archive.serialized_size()).unwrap_or(data.len());
530 let header =
531 Self { header: Self::HEADER, payload_length, is_compressed: compressed.is_some() };
532 let serialized_header: SerializedType3Blob = header.into();
533 writer.write_all(serialized_header.as_bytes())?;
534
535 if let Some(archive) = compressed {
536 archive.write(writer)?;
537 } else {
538 writer.write_all(data)?;
539 }
540 Ok(())
541 }
542
543 pub fn parse(data: &[u8]) -> Result<Option<(Type3Blob, &[u8])>, DeliveryBlobError> {
546 let Ok((serialized_header, payload)) = Ref::<_, SerializedType3Blob>::from_prefix(data)
547 else {
548 return Ok(None);
549 };
550 serialized_header.decode().map(|metadata| Some((metadata, payload)))
551 }
552}
553
554fn generate_blob_to(
555 header_info: DeliveryBlobHeader,
556 options: ChunkedArchiveOptions,
557 data: &[u8],
558 mode: CompressionMode,
559 mut writer: impl std::io::Write,
560) -> Result<(), std::io::Error> {
561 let compressed = match mode {
562 CompressionMode::Attempt | CompressionMode::Always => {
563 let compressed = ChunkedArchive::new(data, options).expect("failed to compress data");
564 if mode == CompressionMode::Always || compressed.serialized_size() <= data.len() {
565 Some(compressed)
566 } else {
567 None
568 }
569 }
570 CompressionMode::Never => None,
571 };
572
573 let payload_length =
574 compressed.as_ref().map(|archive| archive.serialized_size()).unwrap_or(data.len());
575 let blob =
576 DeliveryBlob { header: header_info, payload_length, is_compressed: compressed.is_some() };
577 let serialized_header: SerializedType1Blob = blob.into();
578 writer.write_all(serialized_header.as_bytes())?;
579
580 if let Some(archive) = compressed {
581 archive.write(writer)?;
582 } else {
583 writer.write_all(data)?;
584 }
585 Ok(())
586}
587
588pub const MINIMUM_HEADER_SIZE: u32 = Type1Blob::HEADER.header_length;
589
590#[cfg(test)]
591mod tests {
592
593 use super::*;
594 use rand::Rng;
595
596 const DATA_LEN: usize = 500_000;
597
598 #[test]
599 fn compression_mode_never() {
600 let data: Vec<u8> = vec![0; DATA_LEN];
601 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Never);
602 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
604 assert!(!header.is_compressed);
605 assert_eq!(header.payload_length, data.len());
606 assert_eq!(decompress(&delivery_blob).unwrap(), data);
607 }
608
609 #[test]
610 fn compression_mode_always() {
611 let data: Vec<u8> = {
612 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
613 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
614 };
615 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
616 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
617 assert!(header.is_compressed);
619 assert!(header.payload_length > data.len());
620 assert_eq!(decompress(&delivery_blob).unwrap(), data);
621 }
622
623 #[test]
624 fn compression_mode_attempt_uncompressible() {
625 let data: Vec<u8> = {
626 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
627 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
628 };
629 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
631 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
632 assert!(!header.is_compressed);
633 assert_eq!(header.payload_length, data.len());
634 assert_eq!(decompress(&delivery_blob).unwrap(), data);
635 }
636
637 #[test]
638 fn compression_mode_attempt_compressible() {
639 let data: Vec<u8> = vec![0; DATA_LEN];
640 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
641 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
642 assert!(header.is_compressed);
644 assert!(header.payload_length < data.len());
645 assert_eq!(decompress(&delivery_blob).unwrap(), data);
646 }
647
648 #[test]
649 fn get_decompressed_size() {
650 let data: Vec<u8> = {
651 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
652 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
653 };
654 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
655 assert_eq!(decompressed_size(&delivery_blob).unwrap(), DATA_LEN as u64);
656 assert_eq!(decompressed_size_from_reader(&delivery_blob[..]).unwrap(), DATA_LEN as u64);
657 }
658
659 #[test]
660 fn test_calculate_digest() {
661 let data: Vec<u8> = {
662 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
663 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
664 };
665 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
666 assert_eq!(
667 calculate_digest(&delivery_blob).unwrap(),
668 fuchsia_merkle::root_from_slice(&data)
669 );
670 }
671
672 #[test]
673 fn type_2_round_trip() {
674 let data: Vec<u8> = vec![0x42; DATA_LEN];
675 let delivery_blob = Type2Blob::generate(&data, CompressionMode::Attempt);
676 let (header, _) = Type2Blob::parse(&delivery_blob).unwrap().unwrap();
677 assert_eq!(header.header.delivery_type, DeliveryBlobType::Type2);
678 assert!(header.is_compressed);
679 assert_eq!(decompress(&delivery_blob).unwrap(), data);
680 }
681
682 #[test]
683 fn type_2_vs_type_1_chunk_size() {
684 let data: Vec<u8> = vec![0x42; 256 * 1024];
685 let blob_v1 = Type1Blob::generate(&data, CompressionMode::Always);
686 let blob_v2 = Type2Blob::generate(&data, CompressionMode::Always);
687
688 let (_, payload_v1) = DeliveryBlob::parse(&blob_v1, true).unwrap().unwrap();
689 let (decoded_v1, _) =
690 compression::decode_archive(payload_v1, payload_v1.len()).unwrap().unwrap();
691 assert_eq!(decoded_v1.seek_table().len(), 8);
692
693 let (_, payload_v2) = DeliveryBlob::parse(&blob_v2, true).unwrap().unwrap();
694 let (decoded_v2, _) =
695 compression::decode_archive(payload_v2, payload_v2.len()).unwrap().unwrap();
696 assert_eq!(decoded_v2.seek_table().len(), 2);
697 }
698
699 #[test]
700 fn type_3_compression_mode_never() {
701 let data: Vec<u8> = vec![0; DATA_LEN];
702 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Never);
703 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
704 assert!(!header.is_compressed);
705 assert_eq!(header.payload_length, data.len());
706 assert_eq!(decompress(&delivery_blob).unwrap(), data);
707 }
708
709 #[test]
710 fn type_3_compression_mode_always() {
711 let data: Vec<u8> = {
712 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
713 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
714 };
715 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Always);
716 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
717 assert!(header.is_compressed);
718 assert!(header.payload_length > data.len());
719 assert_eq!(decompress(&delivery_blob).unwrap(), data);
720 }
721
722 #[test]
723 fn type_3_compression_mode_attempt_compressible() {
724 let data: Vec<u8> = vec![0; DATA_LEN];
725 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Attempt);
726 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
727 assert!(header.is_compressed);
728 assert!(header.payload_length < data.len());
729 assert_eq!(decompress(&delivery_blob).unwrap(), data);
730 }
731
732 #[test]
733 fn type_3_get_decompressed_size_and_digest() {
734 let data: Vec<u8> = {
735 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
736 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
737 };
738 let delivery_blob = generate(DeliveryBlobType::Type3, &data);
739 assert_eq!(decompressed_size(&delivery_blob).unwrap(), DATA_LEN as u64);
740 assert_eq!(
741 calculate_digest(&delivery_blob).unwrap(),
742 fuchsia_merkle::root_from_slice(&data)
743 );
744 assert_eq!(decompress(&delivery_blob).unwrap(), data);
745 }
746
747 #[test]
748 fn test_delivery_blob_parse() {
749 let data: Vec<u8> = vec![1, 2, 3, 4];
750 let type1_blob = generate(DeliveryBlobType::Type1, &data);
751 let type3_blob = generate(DeliveryBlobType::Type3, &data);
752
753 assert!(DeliveryBlob::parse(&type1_blob, false).unwrap().is_some());
754 assert!(DeliveryBlob::parse(&type1_blob, true).unwrap().is_some());
755
756 assert_eq!(
757 DeliveryBlob::parse(&type3_blob, false).unwrap_err(),
758 DeliveryBlobError::InvalidType
759 );
760 assert!(DeliveryBlob::parse(&type3_blob, true).unwrap().is_some());
761 }
762}