1use crate::compression::{ChunkedArchive, ChunkedArchiveOptions, ChunkedDecompressor};
16use crate::format::SerializedType1Blob;
17use serde::{Deserialize, Serialize};
18use static_assertions::assert_eq_size;
19use thiserror::Error;
20use zerocopy::{IntoBytes, Ref};
21
22pub mod compression;
23mod format;
24
25assert_eq_size!(usize, u64);
27
28pub fn generate(delivery_type: DeliveryBlobType, data: &[u8]) -> Vec<u8> {
30 match delivery_type {
31 DeliveryBlobType::Type1 => Type1Blob::generate(data, CompressionMode::Attempt),
32 _ => panic!("Unsupported delivery blob type: {:?}", delivery_type),
33 }
34}
35
36pub fn generate_to(
39 delivery_type: DeliveryBlobType,
40 data: &[u8],
41 writer: impl std::io::Write,
42) -> Result<(), std::io::Error> {
43 match delivery_type {
44 DeliveryBlobType::Type1 => Type1Blob::generate_to(data, CompressionMode::Attempt, writer),
45 _ => panic!("Unsupported delivery blob type: {:?}", delivery_type),
46 }
47}
48
49pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
51 let header = DeliveryBlobHeader::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
52 match header.delivery_type {
53 DeliveryBlobType::Type1 => Type1Blob::decompressed_size(delivery_blob),
54 _ => Err(DecompressError::DeliveryBlob(DeliveryBlobError::InvalidType)),
55 }
56}
57
58pub fn decompressed_size_from_reader(
60 mut reader: impl std::io::Read,
61) -> Result<u64, DecompressError> {
62 let mut buf = vec![];
63 loop {
64 let already_read = buf.len();
65 let new_size = already_read + 4096;
66 buf.resize(new_size, 0);
67 let new_size = already_read + reader.read(&mut buf[already_read..new_size])?;
68 if new_size == already_read {
69 return Err(DecompressError::NeedMoreData);
70 }
71 buf.truncate(new_size);
72 match decompressed_size(&buf) {
73 Ok(size) => {
74 return Ok(size);
75 }
76 Err(DecompressError::NeedMoreData) => {}
77 Err(e) => {
78 return Err(e);
79 }
80 }
81 }
82}
83
84pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
86 let header = DeliveryBlobHeader::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
87 match header.delivery_type {
88 DeliveryBlobType::Type1 => Type1Blob::decompress(delivery_blob),
89 _ => Err(DecompressError::DeliveryBlob(DeliveryBlobError::InvalidType)),
90 }
91}
92
93pub fn decompress_to(
96 delivery_blob: &[u8],
97 writer: impl std::io::Write,
98) -> Result<(), DecompressError> {
99 let header = DeliveryBlobHeader::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
100 match header.delivery_type {
101 DeliveryBlobType::Type1 => Type1Blob::decompress_to(delivery_blob, writer),
102 _ => Err(DecompressError::DeliveryBlob(DeliveryBlobError::InvalidType)),
103 }
104}
105
106pub fn calculate_digest(delivery_blob: &[u8]) -> Result<fuchsia_merkle::Hash, DecompressError> {
109 let mut writer = fuchsia_merkle::BufferedMerkleRootBuilder::default();
110 let header = DeliveryBlobHeader::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
111 match header.delivery_type {
112 DeliveryBlobType::Type1 => {
113 let () = Type1Blob::decompress_to(delivery_blob, &mut writer)?;
114 }
115 _ => return Err(DecompressError::DeliveryBlob(DeliveryBlobError::InvalidType)),
116 }
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}
194
195impl TryFrom<u32> for DeliveryBlobType {
196 type Error = DeliveryBlobError;
197 fn try_from(value: u32) -> Result<Self, Self::Error> {
198 match value {
199 value if value == DeliveryBlobType::Reserved as u32 => Ok(DeliveryBlobType::Reserved),
200 value if value == DeliveryBlobType::Type1 as u32 => Ok(DeliveryBlobType::Type1),
201 _ => Err(DeliveryBlobError::InvalidType),
202 }
203 }
204}
205
206impl From<DeliveryBlobType> for u32 {
207 fn from(value: DeliveryBlobType) -> Self {
208 value as u32
209 }
210}
211
212#[derive(Clone, Copy, Debug, Eq, PartialEq)]
214pub enum CompressionMode {
215 Never,
217 Attempt,
219 Always,
221}
222
223#[derive(Clone, Copy, Debug)]
229pub struct Type1Blob {
230 pub header: DeliveryBlobHeader,
232 pub payload_length: usize,
234 pub is_compressed: bool,
235}
236
237impl Type1Blob {
238 pub const HEADER: DeliveryBlobHeader = DeliveryBlobHeader {
239 delivery_type: DeliveryBlobType::Type1,
240 header_length: std::mem::size_of::<SerializedType1Blob>() as u32,
241 };
242
243 pub const CHUNKED_ARCHIVE_OPTIONS: ChunkedArchiveOptions = ChunkedArchiveOptions::V2 {
244 chunk_alignment: fuchsia_merkle::BLOCK_SIZE,
245 minimum_chunk_size: 32 * 1024,
246 compression_level: 14,
247 };
248
249 pub fn generate(data: &[u8], mode: CompressionMode) -> Vec<u8> {
254 let mut delivery_blob: Vec<u8> = vec![];
255 Self::generate_to(data, mode, &mut delivery_blob).unwrap();
256 delivery_blob
257 }
258
259 pub fn generate_to(
265 data: &[u8],
266 mode: CompressionMode,
267 mut writer: impl std::io::Write,
268 ) -> Result<(), std::io::Error> {
269 let compressed = match mode {
271 CompressionMode::Attempt | CompressionMode::Always => {
272 let compressed = ChunkedArchive::new(data, Self::CHUNKED_ARCHIVE_OPTIONS)
273 .expect("failed to compress data");
274 if mode == CompressionMode::Always || compressed.serialized_size() <= data.len() {
275 Some(compressed)
276 } else {
277 None
278 }
279 }
280 CompressionMode::Never => None,
281 };
282
283 let payload_length =
285 compressed.as_ref().map(|archive| archive.serialized_size()).unwrap_or(data.len());
286 let header =
287 Self { header: Type1Blob::HEADER, payload_length, is_compressed: compressed.is_some() };
288 let serialized_header: SerializedType1Blob = header.into();
289 writer.write_all(serialized_header.as_bytes())?;
290
291 if let Some(archive) = compressed {
293 archive.write(writer)?;
294 } else {
295 writer.write_all(data)?;
296 }
297 Ok(())
298 }
299
300 pub fn parse(data: &[u8]) -> Result<Option<(Type1Blob, &[u8])>, DeliveryBlobError> {
305 let Ok((serialized_header, payload)) = Ref::<_, SerializedType1Blob>::from_prefix(data)
306 else {
307 return Ok(None);
308 };
309 serialized_header.decode().map(|metadata| Some((metadata, payload)))
310 }
311
312 pub fn decompressed_size(delivery_blob: &[u8]) -> Result<u64, DecompressError> {
314 let (header, payload) = Self::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
315 if !header.is_compressed {
316 return Ok(header.payload_length as u64);
317 }
318
319 let (decoded_archive, _chunk_data) =
320 compression::decode_archive(payload, header.payload_length)?
321 .ok_or(DecompressError::NeedMoreData)?;
322 Ok(decoded_archive.decompressed_size() as u64)
323 }
324
325 pub fn decompress(delivery_blob: &[u8]) -> Result<Vec<u8>, DecompressError> {
327 let mut decompressed = vec![];
328 decompressed.reserve(Self::decompressed_size(delivery_blob)? as usize);
329 Self::decompress_to(delivery_blob, &mut decompressed)?;
330 Ok(decompressed)
331 }
332
333 pub fn decompress_to(
335 delivery_blob: &[u8],
336 mut writer: impl std::io::Write,
337 ) -> Result<(), DecompressError> {
338 let (header, payload) = Self::parse(delivery_blob)?.ok_or(DecompressError::NeedMoreData)?;
339 if !header.is_compressed {
340 return Ok(writer.write_all(payload)?);
341 }
342
343 let (decoded_archive, chunk_data) =
344 compression::decode_archive(payload, header.payload_length)?
345 .ok_or(DecompressError::NeedMoreData)?;
346 let mut decompressor = ChunkedDecompressor::new(decoded_archive)?;
347 let mut result = Ok(());
348 let mut chunk_callback = |chunk: &[u8]| {
349 if let Err(e) = writer.write_all(chunk) {
350 result = Err(e.into());
351 }
352 };
353 decompressor.update(chunk_data, &mut chunk_callback)?;
354 result
355 }
356}
357
358#[cfg(test)]
359mod tests {
360
361 use super::*;
362 use rand::Rng;
363
364 const DATA_LEN: usize = 500_000;
365
366 #[test]
367 fn compression_mode_never() {
368 let data: Vec<u8> = vec![0; DATA_LEN];
369 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Never);
370 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
372 assert!(!header.is_compressed);
373 assert_eq!(header.payload_length, data.len());
374 assert_eq!(Type1Blob::decompress(&delivery_blob).unwrap(), data);
375 }
376
377 #[test]
378 fn compression_mode_always() {
379 let data: Vec<u8> = {
380 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
381 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
382 };
383 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
384 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
385 assert!(header.is_compressed);
387 assert!(header.payload_length > data.len());
388 assert_eq!(Type1Blob::decompress(&delivery_blob).unwrap(), data);
389 }
390
391 #[test]
392 fn compression_mode_attempt_uncompressible() {
393 let data: Vec<u8> = {
394 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
395 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
396 };
397 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
399 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
400 assert!(!header.is_compressed);
401 assert_eq!(header.payload_length, data.len());
402 assert_eq!(Type1Blob::decompress(&delivery_blob).unwrap(), data);
403 }
404
405 #[test]
406 fn compression_mode_attempt_compressible() {
407 let data: Vec<u8> = vec![0; DATA_LEN];
408 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Attempt);
409 let (header, _) = Type1Blob::parse(&delivery_blob).unwrap().unwrap();
410 assert!(header.is_compressed);
412 assert!(header.payload_length < data.len());
413 assert_eq!(Type1Blob::decompress(&delivery_blob).unwrap(), data);
414 }
415
416 #[test]
417 fn get_decompressed_size() {
418 let data: Vec<u8> = {
419 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
420 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
421 };
422 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
423 assert_eq!(decompressed_size(&delivery_blob).unwrap(), DATA_LEN as u64);
424 assert_eq!(decompressed_size_from_reader(&delivery_blob[..]).unwrap(), DATA_LEN as u64);
425 }
426
427 #[test]
428 fn test_calculate_digest() {
429 let data: Vec<u8> = {
430 let range = rand::distr::Uniform::<u8>::new_inclusive(0, 255).unwrap();
431 rand::rng().sample_iter(&range).take(DATA_LEN).collect()
432 };
433 let delivery_blob = Type1Blob::generate(&data, CompressionMode::Always);
434 assert_eq!(
435 calculate_digest(&delivery_blob).unwrap(),
436 fuchsia_merkle::root_from_slice(&data)
437 );
438 }
439}