heddle-object-model 0.15.0

Heddle's content-addressed object model and stable codecs.
Documentation
// SPDX-License-Identifier: Apache-2.0

use super::{
    decode_blob_frame, decode_state_frame, decode_tree_frame, encode_state_frame,
    limits::MAX_COMPACT_COUNT,
};
use crate::object::{Attribution, ContentHash, Principal, State};

#[test]
fn blob_frame_rejects_declared_count_above_object_cap() {
    let mut payload = Vec::new();
    put_u64(&mut payload, (MAX_COMPACT_COUNT + 1) as u64);
    let error = decode_blob_frame(&frame(b"HCB2", &payload)).unwrap_err();
    assert!(
        error.to_string().contains("exceeds maximum"),
        "hostile blob count must fail at the count gate before with_capacity, got {error}"
    );
}

#[test]
fn tree_entry_count_that_fits_remaining_bytes_still_fails_encoded_floor() {
    let mut payload = Vec::new();
    put_u64(&mut payload, 1);
    put_u64(&mut payload, 100);
    payload.resize(payload.len() + 100, 0);
    let error = decode_tree_frame(&frame(b"HCT1", &payload)).unwrap_err();
    assert!(
        error.to_string().contains("bytes per item"),
        "tree entry count that only fits a 1-byte remaining() check must fail before with_capacity, got {error}"
    );
}

#[test]
fn state_frame_count_that_fits_remaining_bytes_still_fails_column_floor() {
    let mut payload = Vec::new();
    put_u64(&mut payload, 200);
    payload.resize(payload.len() + 200, 0);
    let error = decode_state_frame(&frame(b"HCS1", &payload)).unwrap_err();
    assert!(
        error.to_string().contains("bytes per item"),
        "state count that only fits a 1-byte remaining() check must fail before blank_state materialization, got {error}"
    );
}

#[test]
fn minimal_state_frame_survives_column_floor() {
    let mut state = State::new(
        ContentHash::from_bytes([1; 32]),
        Vec::new(),
        Attribution::human(Principal::new("A", "a@b.c")),
    );
    state.state_id = state.id();
    decode_state_frame(&encode_state_frame(&[state]).unwrap()).unwrap();
}

fn frame(magic: &[u8; 4], after_magic: &[u8]) -> Vec<u8> {
    let mut bytes = Vec::with_capacity(4 + after_magic.len() + 32);
    bytes.extend_from_slice(magic);
    bytes.extend_from_slice(after_magic);
    let digest = blake3::hash(&bytes);
    bytes.extend_from_slice(digest.as_bytes());
    bytes
}

fn put_u64(out: &mut Vec<u8>, mut value: u64) {
    while value >= 0x80 {
        out.push((value as u8) | 0x80);
        value >>= 7;
    }
    out.push(value as u8);
}