Skip to main content

heddle_object_model/compact/
blob.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use super::{
4    Result,
5    io::{Reader, Writer},
6};
7use crate::object::ContentHash;
8
9const BLOB_MAGIC: &[u8; 4] = b"HCB2";
10
11/// One verified blob slice within a lineage-solid frame.
12pub type DecodedBlob<'a> = (ContentHash, &'a [u8]);
13
14/// Whether `bytes` begin with the lineage-solid blob-frame discriminator.
15pub fn is_blob_frame(bytes: &[u8]) -> bool {
16    bytes.starts_with(BLOB_MAGIC)
17}
18
19/// Encode blob bodies newest-to-oldest in one checksummed solid frame.
20///
21/// Lengths precede the concatenated bodies, giving the frame reader an exact
22/// `(offset, len)` for every object while zstd sees one continuous lineage.
23pub 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
43/// Decode and whole-frame-verify every indexed blob slice.
44pub 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}