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