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 delivery_blob_type(data: &[u8]) -> Result<DeliveryBlobType, DecompressError> {
60 let header = DeliveryBlobHeader::parse(data)?.ok_or(DecompressError::NeedMoreData)?;
61 Ok(header.delivery_type)
62}
63
64pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
66 DeliveryBlob::decompressed_size(delivery_blob)
67}
68
69pub fn decompressed_size_from_reader(
71 mut reader: impl std::io::Read,
72) -> Result<u64, DecompressError> {
73 let mut buf = vec![];
74 loop {
75 let already_read = buf.len();
76 let new_size = already_read + 4096;
77 buf.resize(new_size, 0);
78 let new_size = already_read + reader.read(&mut buf[already_read..new_size])?;
79 if new_size == already_read {
80 return Err(DecompressError::NeedMoreData);
81 }
82 buf.truncate(new_size);
83 match decompressed_size(&buf) {
84 Ok(size) => {
85 return Ok(size);
86 }
87 Err(DecompressError::NeedMoreData) => {}
88 Err(e) => {
89 return Err(e);
90 }
91 }
92 }
93}
94
95pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
97 DeliveryBlob::decompress(delivery_blob)
98}
99
100pub fn decompress_to(
103 delivery_blob: &[u8],
104 writer: impl std::io::Write,
105) -> Result<(), DecompressError> {
106 DeliveryBlob::decompress_to(delivery_blob, writer)
107}
108
109pub fn calculate_digest(delivery_blob: &[u8]) -> Result<fuchsia_merkle::Hash, DecompressError> {
112 let mut writer = fuchsia_merkle::BufferedMerkleRootBuilder::default();
113 let () = DeliveryBlob::decompress_to(delivery_blob, &mut writer)?;
114 Ok(writer.complete())
115}
116
117#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
118pub enum DeliveryBlobError {
119 #[error("Invalid or unsupported delivery blob type.")]
120 InvalidType,
121
122 #[error("Delivery blob header has incorrect magic.")]
123 BadMagic,
124
125 #[error("Integrity/checksum or other validity checks failed.")]
126 IntegrityError,
127}
128
129#[derive(Debug, Error)]
130pub enum DecompressError {
131 #[error("DeliveryBlob error")]
132 DeliveryBlob(#[from] DeliveryBlobError),
133
134 #[error("ChunkedArchive error")]
135 ChunkedArchive(#[from] compression::ChunkedArchiveError),
136
137 #[error("Need more data")]
138 NeedMoreData,
139
140 #[error("io error")]
141 IoError(#[from] std::io::Error),
142}
143
144#[cfg(target_os = "fuchsia")]
145impl From<DeliveryBlobError> for zx::Status {
146 fn from(value: DeliveryBlobError) -> Self {
147 match value {
148 DeliveryBlobError::InvalidType => zx::Status::NOT_SUPPORTED,
150 DeliveryBlobError::BadMagic | DeliveryBlobError::IntegrityError => {
152 zx::Status::IO_DATA_INTEGRITY
153 }
154 }
155 }
156}
157
158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub struct DeliveryBlobHeader {
161 pub delivery_type: DeliveryBlobType,
162 pub header_length: u32,
163}
164
165impl DeliveryBlobHeader {
166 pub fn parse(data: &[u8]) -> Result<Option<DeliveryBlobHeader>, DeliveryBlobError> {
170 let Ok((serialized_header, _metadata_and_payload)) =
171 Ref::<_, format::SerializedHeader>::from_prefix(data)
172 else {
173 return Ok(None);
174 };
175 serialized_header.decode().map(Some)
176 }
177}
178
179#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
184#[repr(u32)]
185pub enum DeliveryBlobType {
186 Reserved = 0,
188 Type1 = 1,
190 Type2 = 2,
192 Type3 = 3,
195}
196
197impl TryFrom<u32> for DeliveryBlobType {
198 type Error = DeliveryBlobError;
199 fn try_from(value: u32) -> Result<Self, Self::Error> {
200 match value {
201 value if value == DeliveryBlobType::Reserved as u32 => Ok(DeliveryBlobType::Reserved),
202 value if value == DeliveryBlobType::Type1 as u32 => Ok(DeliveryBlobType::Type1),
203 value if value == DeliveryBlobType::Type2 as u32 => Ok(DeliveryBlobType::Type2),
204 value if value == DeliveryBlobType::Type3 as u32 => Ok(DeliveryBlobType::Type3),
205 _ => Err(DeliveryBlobError::InvalidType),
206 }
207 }
208}
209
210impl From<DeliveryBlobType> for u32 {
211 fn from(value: DeliveryBlobType) -> Self {
212 value as u32
213 }
214}
215
216#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218pub enum CompressionMode {
219 Never,
221 Attempt,
223 Always,
225}
226
227#[derive(Clone, Copy, Debug, PartialEq, Eq)]
229pub struct DeliveryBlob {
230 pub header: DeliveryBlobHeader,
231 pub payload_length: usize,
232 pub is_compressed: bool,
233}
234
235impl DeliveryBlob {
236 pub fn parse(
242 data: &[u8],
243 allow_type3: bool,
244 ) -> Result<Option<(DeliveryBlob, &[u8])>, DeliveryBlobError> {
245 let Some(header) = DeliveryBlobHeader::parse(data)? else {
246 return Ok(None);
247 };
248 match header.delivery_type {
249 DeliveryBlobType::Type1 | DeliveryBlobType::Type2 => {
250 let Ok((serialized_header, payload)) =
251 Ref::<_, format::SerializedType1Blob>::from_prefix(data)
252 else {
253 return Ok(None);
254 };
255 serialized_header.decode().map(|metadata| Some((metadata, payload)))
256 }
257 DeliveryBlobType::Type3 => {
258 if !allow_type3 {
259 return Err(DeliveryBlobError::InvalidType);
260 }
261 let Ok((serialized_header, payload)) =
262 Ref::<_, format::SerializedType3Blob>::from_prefix(data)
263 else {
264 return Ok(None);
265 };
266 serialized_header.decode().map(|metadata| Some((metadata.into(), payload)))
267 }
268 _ => Err(DeliveryBlobError::InvalidType),
269 }
270 }
271
272 pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
274 let (header, payload) =
275 Self::parse(delivery_blob, true)?.ok_or(DecompressError::NeedMoreData)?;
276 if !header.is_compressed {
277 return Ok(header.payload_length as u64);
278 }
279
280 let (decoded_archive, _chunk_data) =
281 compression::decode_archive(payload, header.payload_length)?
282 .ok_or(DecompressError::NeedMoreData)?;
283 Ok(decoded_archive.decompressed_size() as u64)
284 }
285
286 pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
288 let mut decompressed = vec![];
289 decompressed.reserve(Self::decompressed_size(delivery_blob)? as usize);
290 Self::decompress_to(delivery_blob, &mut decompressed)?;
291 Ok(decompressed)
292 }
293
294 pub fn decompress_to(
296 delivery_blob: &[u8],
297 mut writer: impl std::io::Write,
298 ) -> Result<(), DecompressError> {
299 let (header, payload) =
300 Self::parse(delivery_blob, true)?.ok_or(DecompressError::NeedMoreData)?;
301 if !header.is_compressed {
302 return Ok(writer.write_all(payload)?);
303 }
304
305 let (decoded_archive, chunk_data) =
306 compression::decode_archive(payload, header.payload_length)?
307 .ok_or(DecompressError::NeedMoreData)?;
308 let mut decompressor = ChunkedDecompressor::new(decoded_archive)?;
309 let mut result = Ok(());
310 let mut chunk_callback = |chunk: &[u8]| {
311 if let Err(e) = writer.write_all(chunk) {
312 result = Err(e.into());
313 }
314 };
315 decompressor.update(chunk_data, &mut chunk_callback)?;
316 result
317 }
318}
319
320#[derive(Clone, Copy, Debug, PartialEq, Eq)]
326pub struct Type1Blob {
327 pub header: DeliveryBlobHeader,
328 pub payload_length: usize,
329 pub is_compressed: bool,
330}
331
332impl From<DeliveryBlob> for Type1Blob {
333 fn from(blob: DeliveryBlob) -> Self {
334 Self {
335 header: blob.header,
336 payload_length: blob.payload_length,
337 is_compressed: blob.is_compressed,
338 }
339 }
340}
341
342impl From<Type1Blob> for DeliveryBlob {
343 fn from(blob: Type1Blob) -> Self {
344 Self {
345 header: blob.header,
346 payload_length: blob.payload_length,
347 is_compressed: blob.is_compressed,
348 }
349 }
350}
351
352impl Type1Blob {
353 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
354 delivery_type: DeliveryBlobType::Type1,
355 header_length: std::mem::size_of::<SerializedType1Blob>() as u32,
356 };
357
358 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions = ChunkedArchiveOptions::V2 {
359 chunk_alignment: fuchsia_merkle::BLOCK_SIZE,
360 minimum_chunk_size: 32 * 1024,
361 compression_level: 14,
362 };
363
364 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
366 let mut delivery_blob: Vec<u8> = vec![];
367 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
368 delivery_blob
369 }
370
371 pub fn generate_to(
374 data: &[u8],
375 mode: CompressionMode,
376 writer: impl std::io::Write,
377 ) -> Result<(), std::io::Error> {
378 generate_blob_to(Self::HEADER, Self::CHUNKED_ARCHIVE_OPTIONS, data, mode, writer)
379 }
380
381 pub fn parse(data: &[u8]) -> Result<Option<(Type1Blob, &[u8])>, DeliveryBlobError> {
384 match DeliveryBlob::parse(data, true)? {
385 Some((blob, payload)) if blob.header.delivery_type == DeliveryBlobType::Type1 => {
386 Ok(Some((blob.into(), payload)))
387 }
388 Some(_) => Err(DeliveryBlobError::InvalidType),
389 None => Ok(None),
390 }
391 }
392}
393
394#[derive(Clone, Copy, Debug, PartialEq, Eq)]
400pub struct Type2Blob {
401 pub header: DeliveryBlobHeader,
402 pub payload_length: usize,
403 pub is_compressed: bool,
404}
405
406impl From<DeliveryBlob> for Type2Blob {
407 fn from(blob: DeliveryBlob) -> Self {
408 Self {
409 header: blob.header,
410 payload_length: blob.payload_length,
411 is_compressed: blob.is_compressed,
412 }
413 }
414}
415
416impl From<Type2Blob> for DeliveryBlob {
417 fn from(blob: Type2Blob) -> Self {
418 Self {
419 header: blob.header,
420 payload_length: blob.payload_length,
421 is_compressed: blob.is_compressed,
422 }
423 }
424}
425
426impl Type2Blob {
427 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
428 delivery_type: DeliveryBlobType::Type2,
429 header_length: std::mem::size_of::<SerializedType1Blob>() as u32,
430 };
431
432 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions = ChunkedArchiveOptions::V2 {
433 chunk_alignment: fuchsia_merkle::BLOCK_SIZE,
434 minimum_chunk_size: 128 * 1024,
435 compression_level: 21,
436 };
437
438 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
440 let mut delivery_blob: Vec<u8> = vec![];
441 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
442 delivery_blob
443 }
444
445 pub fn generate_to(
448 data: &[u8],
449 mode: CompressionMode,
450 writer: impl std::io::Write,
451 ) -> Result<(), std::io::Error> {
452 generate_blob_to(Self::HEADER, Self::CHUNKED_ARCHIVE_OPTIONS, data, mode, writer)
453 }
454
455 pub fn parse(data: &[u8]) -> Result<Option<(Type2Blob, &[u8])>, DeliveryBlobError> {
458 match DeliveryBlob::parse(data, true)? {
459 Some((blob, payload)) if blob.header.delivery_type == DeliveryBlobType::Type2 => {
460 Ok(Some((blob.into(), payload)))
461 }
462 Some(_) => Err(DeliveryBlobError::InvalidType),
463 None => Ok(None),
464 }
465 }
466}
467
468#[derive(Clone, Copy, Debug, PartialEq, Eq)]
476pub struct Type3Blob {
477 pub header: DeliveryBlobHeader,
478 pub payload_length: usize,
479 pub is_compressed: bool,
480}
481
482impl From<DeliveryBlob> for Type3Blob {
483 fn from(blob: DeliveryBlob) -> Self {
484 Self {
485 header: blob.header,
486 payload_length: blob.payload_length,
487 is_compressed: blob.is_compressed,
488 }
489 }
490}
491
492impl From<Type3Blob> for DeliveryBlob {
493 fn from(blob: Type3Blob) -> Self {
494 Self {
495 header: blob.header,
496 payload_length: blob.payload_length,
497 is_compressed: blob.is_compressed,
498 }
499 }
500}
501
502impl Type3Blob {
503 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
504 delivery_type: DeliveryBlobType::Type3,
505 header_length: std::mem::size_of::<SerializedType3Blob>() as u32,
506 };
507
508 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions =
509 ChunkedArchiveOptions::V3 { compression_algorithm: compression::CompressionAlgorithm::Lz4 };
510
511 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
513 let mut delivery_blob: Vec<u8> = vec![];
514 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
515 delivery_blob
516 }
517
518 pub fn generate_to(
521 data: &[u8],
522 mode: CompressionMode,
523 mut writer: impl std::io::Write,
524 ) -> Result<(), std::io::Error> {
525 let compressed = match mode {
526 CompressionMode::Attempt | CompressionMode::Always => {
527 let compressed = ChunkedArchive::new(data, Self::CHUNKED_ARCHIVE_OPTIONS)
528 .expect("failed to compress data");
529 if mode == CompressionMode::Always || compressed.serialized_size() <= data.len() {
530 Some(compressed)
531 } else {
532 None
533 }
534 }
535 CompressionMode::Never => None,
536 };
537
538 let payload_length =
539 compressed.as_ref().map(|archive| archive.serialized_size()).unwrap_or(data.len());
540 let header =
541 Self { header: Self::HEADER, payload_length, is_compressed: compressed.is_some() };
542 let serialized_header: SerializedType3Blob = header.into();
543 writer.write_all(serialized_header.as_bytes())?;
544
545 if let Some(archive) = compressed {
546 archive.write(writer)?;
547 } else {
548 writer.write_all(data)?;
549 }
550 Ok(())
551 }
552
553 pub fn parse(data: &[u8]) -> Result<Option<(Type3Blob, &[u8])>, DeliveryBlobError> {
556 let Ok((serialized_header, payload)) = Ref::<_, SerializedType3Blob>::from_prefix(data)
557 else {
558 return Ok(None);
559 };
560 serialized_header.decode().map(|metadata| Some((metadata, payload)))
561 }
562}
563
564fn generate_blob_to(
565 header_info: DeliveryBlobHeader,
566 options: ChunkedArchiveOptions,
567 data: &[u8],
568 mode: CompressionMode,
569 mut writer: impl std::io::Write,
570) -> Result<(), std::io::Error> {
571 let compressed = match mode {
572 CompressionMode::Attempt | CompressionMode::Always => {
573 let compressed = ChunkedArchive::new(data, options).expect("failed to compress data");
574 if mode == CompressionMode::Always || compressed.serialized_size() <= data.len() {
575 Some(compressed)
576 } else {
577 None
578 }
579 }
580 CompressionMode::Never => None,
581 };
582
583 let payload_length =
584 compressed.as_ref().map(|archive| archive.serialized_size()).unwrap_or(data.len());
585 let blob =
586 DeliveryBlob { header: header_info, payload_length, is_compressed: compressed.is_some() };
587 let serialized_header: SerializedType1Blob = blob.into();
588 writer.write_all(serialized_header.as_bytes())?;
589
590 if let Some(archive) = compressed {
591 archive.write(writer)?;
592 } else {
593 writer.write_all(data)?;
594 }
595 Ok(())
596}
597
598pub const MINIMUM_HEADER_SIZE: u32 = Type1Blob::HEADER.header_length;
599
600#[cfg(test)]
601mod tests {
602
603 use super::*;
604 use rand::Rng;
605
606 const DATA_LEN: usize = 500_000;
607
608 #[test]
609 fn compression_mode_never() {
610 let data: Vec<u8> = vec![0; DATA_LEN];
611 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Never);
612 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
614 assert!(!header.is_compressed);
615 assert_eq!(header.payload_length, data.len());
616 assert_eq!(decompress(&delivery_blob).unwrap(), data);
617 }
618
619 #[test]
620 fn compression_mode_always() {
621 let data: Vec<u8> = {
622 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
623 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
624 };
625 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
626 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
627 assert!(header.is_compressed);
629 assert!(header.payload_length > data.len());
630 assert_eq!(decompress(&delivery_blob).unwrap(), data);
631 }
632
633 #[test]
634 fn compression_mode_attempt_uncompressible() {
635 let data: Vec<u8> = {
636 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
637 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
638 };
639 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
641 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
642 assert!(!header.is_compressed);
643 assert_eq!(header.payload_length, data.len());
644 assert_eq!(decompress(&delivery_blob).unwrap(), data);
645 }
646
647 #[test]
648 fn compression_mode_attempt_compressible() {
649 let data: Vec<u8> = vec![0; DATA_LEN];
650 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
651 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
652 assert!(header.is_compressed);
654 assert!(header.payload_length < data.len());
655 assert_eq!(decompress(&delivery_blob).unwrap(), data);
656 }
657
658 #[test]
659 fn get_decompressed_size() {
660 let data: Vec<u8> = {
661 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
662 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
663 };
664 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
665 assert_eq!(decompressed_size(&delivery_blob).unwrap(), DATA_LEN as u64);
666 assert_eq!(decompressed_size_from_reader(&delivery_blob[..]).unwrap(), DATA_LEN as u64);
667 }
668
669 #[test]
670 fn get_delivery_blob_type() {
671 let delivery_blob = Type1Blob::generate(&[], CompressionMode::Never);
672 assert_eq!(delivery_blob_type(&delivery_blob).unwrap(), DeliveryBlobType::Type1);
673 }
674
675 #[test]
676 fn get_delivery_blob_type_truncated() {
677 let delivery_blob = Type1Blob::generate(&[], CompressionMode::Never);
678 assert!(matches!(
680 delivery_blob_type(&delivery_blob[..2]).unwrap_err(),
681 DecompressError::NeedMoreData
682 ));
683 }
684
685 #[test]
686 fn get_delivery_blob_type_corrupted() {
687 let bad_data = vec![0xFF; 64];
689
690 assert!(matches!(
692 delivery_blob_type(&bad_data).unwrap_err(),
693 DecompressError::DeliveryBlob(_)
694 ));
695 }
696
697 #[test]
698 fn test_calculate_digest() {
699 let data: Vec<u8> = {
700 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
701 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
702 };
703 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
704 assert_eq!(
705 calculate_digest(&delivery_blob).unwrap(),
706 fuchsia_merkle::root_from_slice(&data)
707 );
708 }
709
710 #[test]
711 fn type_2_round_trip() {
712 let data: Vec<u8> = vec![0x42; DATA_LEN];
713 let delivery_blob = Type2Blob::generate(&data, CompressionMode::Attempt);
714 let (header, _) = Type2Blob::parse(&delivery_blob).unwrap().unwrap();
715 assert_eq!(header.header.delivery_type, DeliveryBlobType::Type2);
716 assert!(header.is_compressed);
717 assert_eq!(decompress(&delivery_blob).unwrap(), data);
718 }
719
720 #[test]
721 fn type_2_vs_type_1_chunk_size() {
722 let data: Vec<u8> = vec![0x42; 256 * 1024];
723 let blob_v1 = Type1Blob::generate(&data, CompressionMode::Always);
724 let blob_v2 = Type2Blob::generate(&data, CompressionMode::Always);
725
726 let (_, payload_v1) = DeliveryBlob::parse(&blob_v1, true).unwrap().unwrap();
727 let (decoded_v1, _) =
728 compression::decode_archive(payload_v1, payload_v1.len()).unwrap().unwrap();
729 assert_eq!(decoded_v1.seek_table().len(), 8);
730
731 let (_, payload_v2) = DeliveryBlob::parse(&blob_v2, true).unwrap().unwrap();
732 let (decoded_v2, _) =
733 compression::decode_archive(payload_v2, payload_v2.len()).unwrap().unwrap();
734 assert_eq!(decoded_v2.seek_table().len(), 2);
735 }
736
737 #[test]
738 fn type_3_compression_mode_never() {
739 let data: Vec<u8> = vec![0; DATA_LEN];
740 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Never);
741 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
742 assert!(!header.is_compressed);
743 assert_eq!(header.payload_length, data.len());
744 assert_eq!(decompress(&delivery_blob).unwrap(), data);
745 }
746
747 #[test]
748 fn type_3_compression_mode_always() {
749 let data: Vec<u8> = {
750 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
751 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
752 };
753 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Always);
754 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
755 assert!(header.is_compressed);
756 assert!(header.payload_length > data.len());
757 assert_eq!(decompress(&delivery_blob).unwrap(), data);
758 }
759
760 #[test]
761 fn type_3_compression_mode_attempt_compressible() {
762 let data: Vec<u8> = vec![0; DATA_LEN];
763 let delivery_blob = Type3Blob::generate(&data, CompressionMode::Attempt);
764 let (header, _) = Type3Blob::parse(&delivery_blob).unwrap().unwrap();
765 assert!(header.is_compressed);
766 assert!(header.payload_length < data.len());
767 assert_eq!(decompress(&delivery_blob).unwrap(), data);
768 }
769
770 #[test]
771 fn type_3_get_decompressed_size_and_digest() {
772 let data: Vec<u8> = {
773 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
774 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
775 };
776 let delivery_blob = generate(DeliveryBlobType::Type3, &data);
777 assert_eq!(decompressed_size(&delivery_blob).unwrap(), DATA_LEN as u64);
778 assert_eq!(
779 calculate_digest(&delivery_blob).unwrap(),
780 fuchsia_merkle::root_from_slice(&data)
781 );
782 assert_eq!(decompress(&delivery_blob).unwrap(), data);
783 }
784
785 #[test]
786 fn test_delivery_blob_parse() {
787 let data: Vec<u8> = vec![1, 2, 3, 4];
788 let type1_blob = generate(DeliveryBlobType::Type1, &data);
789 let type3_blob = generate(DeliveryBlobType::Type3, &data);
790
791 assert!(DeliveryBlob::parse(&type1_blob, false).unwrap().is_some());
792 assert!(DeliveryBlob::parse(&type1_blob, true).unwrap().is_some());
793
794 assert_eq!(
795 DeliveryBlob::parse(&type3_blob, false).unwrap_err(),
796 DeliveryBlobError::InvalidType
797 );
798 assert!(DeliveryBlob::parse(&type3_blob, true).unwrap().is_some());
799 }
800}