heddle_object_model/compact/
blob.rs1use super::{
4 Result,
5 io::{Reader, Writer},
6};
7use crate::object::ContentHash;
8
9const BLOB_MAGIC: &[u8; 4] = b"HCB2";
10
11pub type DecodedBlob<'a> = (ContentHash, &'a [u8]);
13
14pub fn is_blob_frame(bytes: &[u8]) -> bool {
16 bytes.starts_with(BLOB_MAGIC)
17}
18
19pub fn encode_blob_frame(blobs: &[&[u8]]) -> Result<Vec<u8>> {
24 let mut output = Writer::new(BLOB_MAGIC);
25 output.put_u64(blobs.len() as u64);
26 if let Some((first, rest)) = blobs.split_first() {
27 output.put_u64(first.len() as u64);
28 let mut previous = i64::try_from(first.len())
29 .map_err(|_| super::invalid("blob length exceeds signed delta range"))?;
30 for blob in rest {
31 let current = i64::try_from(blob.len())
32 .map_err(|_| super::invalid("blob length exceeds signed delta range"))?;
33 output.put_i64(current - previous);
34 previous = current;
35 }
36 }
37 for blob in blobs {
38 output.put_fixed(blob);
39 }
40 Ok(output.finish())
41}
42
43pub fn decode_blob_frame(bytes: &[u8]) -> Result<Vec<DecodedBlob<'_>>> {
45 let mut input = Reader::verified(bytes, BLOB_MAGIC)?;
46 let count = input.get_count("blob frame")?;
47 let mut lengths = Vec::with_capacity(count);
48 if count > 0 {
49 let first = input.get_u64()?;
50 lengths.push(checked_length(first)?);
51 let mut previous = i64::try_from(first)
52 .map_err(|_| super::invalid("blob length exceeds signed delta range"))?;
53 for _ in 1..count {
54 let current = previous
55 .checked_add(input.get_i64()?)
56 .ok_or_else(|| super::invalid("blob length delta overflow"))?;
57 if current < 0 {
58 return Err(super::invalid("blob length delta became negative"));
59 }
60 lengths.push(checked_length(current as u64)?);
61 previous = current;
62 }
63 }
64 let minimum_remaining = lengths
65 .iter()
66 .try_fold(0usize, |total, len| total.checked_add(*len))
67 .ok_or_else(|| super::invalid("blob frame length overflow"))?;
68 if input.remaining() != minimum_remaining {
69 return Err(super::invalid(format!(
70 "blob lengths total {minimum_remaining}, frame has {} body bytes",
71 input.remaining()
72 )));
73 }
74 let mut blobs = Vec::with_capacity(count);
75 for len in lengths {
76 let body = input.take(len)?;
77 blobs.push((ContentHash::compute_typed("blob", body), body));
78 }
79 input.finish()?;
80 Ok(blobs)
81}
82
83fn checked_length(value: u64) -> Result<usize> {
84 usize::try_from(value).map_err(|_| super::invalid("blob length exceeds platform limits"))
85}