use crate::digest::ValueDigest;
use crate::storage::NodeStorage;
pub const EXTERNAL_MAGIC: [u8; 4] = [0x00, 0xFF, b'P', b'X'];
pub const SIZE_FIELD_BYTES: usize = 8;
pub const fn envelope_len<const N: usize>() -> usize {
EXTERNAL_MAGIC.len() + N + SIZE_FIELD_BYTES
}
pub fn make_envelope<const N: usize>(hash: &ValueDigest<N>, original_size: u64) -> Vec<u8> {
let mut buf = Vec::with_capacity(envelope_len::<N>());
buf.extend_from_slice(&EXTERNAL_MAGIC);
buf.extend_from_slice(hash.as_bytes());
buf.extend_from_slice(&original_size.to_le_bytes());
buf
}
pub fn parse_envelope<const N: usize>(bytes: &[u8]) -> Option<(ValueDigest<N>, u64)> {
if bytes.len() != envelope_len::<N>() {
return None;
}
if bytes[..EXTERNAL_MAGIC.len()] != EXTERNAL_MAGIC {
return None;
}
let hash_bytes = &bytes[EXTERNAL_MAGIC.len()..EXTERNAL_MAGIC.len() + N];
let hash = ValueDigest::<N>::raw_hash(hash_bytes);
let size_bytes: [u8; SIZE_FIELD_BYTES] = bytes
[EXTERNAL_MAGIC.len() + N..EXTERNAL_MAGIC.len() + N + SIZE_FIELD_BYTES]
.try_into()
.ok()?;
let original_size = u64::from_le_bytes(size_bytes);
Some((hash, original_size))
}
pub fn unwrap_value<const N: usize, S: NodeStorage<N>>(raw: &[u8], storage: &S) -> Vec<u8> {
if let Some((hash, _original_size)) = parse_envelope::<N>(raw) {
if let Some(blob) = storage.get_blob(&hash) {
return blob;
}
}
raw.to_vec()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::InMemoryNodeStorage;
#[test]
fn envelope_len_for_n32_is_44() {
assert_eq!(envelope_len::<32>(), 44);
assert_eq!(EXTERNAL_MAGIC.len(), 4);
assert_eq!(SIZE_FIELD_BYTES, 8);
}
#[test]
fn make_envelope_has_expected_shape() {
let hash = ValueDigest::<32>::new(b"payload");
let env = make_envelope::<32>(&hash, 1234);
assert_eq!(env.len(), 44);
assert_eq!(&env[..4], &EXTERNAL_MAGIC);
assert_eq!(&env[4..36], hash.as_bytes());
assert_eq!(u64::from_le_bytes(env[36..44].try_into().unwrap()), 1234);
}
#[test]
fn parse_envelope_round_trips() {
let hash = ValueDigest::<32>::new(b"payload");
let env = make_envelope::<32>(&hash, 42);
let (got_hash, got_size) = parse_envelope::<32>(&env).expect("should parse");
assert_eq!(got_hash, hash);
assert_eq!(got_size, 42);
}
#[test]
fn parse_envelope_rejects_wrong_length() {
assert!(parse_envelope::<32>(b"too short").is_none());
let too_long = [0u8; 100];
assert!(parse_envelope::<32>(&too_long).is_none());
}
#[test]
fn parse_envelope_rejects_wrong_magic() {
let mut bytes = [0u8; 44];
bytes[..4].copy_from_slice(b"NOPE");
assert!(parse_envelope::<32>(&bytes).is_none());
}
#[test]
fn parse_envelope_44_byte_random_user_value_is_likely_rejected() {
let payload = b"this is exactly forty-four byte value!......".to_vec();
assert_eq!(payload.len(), 44);
assert!(parse_envelope::<32>(&payload).is_none());
}
#[test]
fn unwrap_value_returns_blob_when_hash_present() {
let mut storage = InMemoryNodeStorage::<32>::new();
let payload = b"the original large payload".to_vec();
let hash = ValueDigest::<32>::new(&payload);
storage.insert_blob(hash.clone(), &payload).unwrap();
let env = make_envelope::<32>(&hash, payload.len() as u64);
assert_eq!(unwrap_value::<32, _>(&env, &storage), payload);
}
#[test]
fn unwrap_value_returns_raw_when_not_envelope() {
let storage = InMemoryNodeStorage::<32>::new();
let raw = b"a small inline value".to_vec();
assert_eq!(unwrap_value::<32, _>(&raw, &storage), raw);
}
#[test]
fn unwrap_value_falls_back_when_envelope_hash_missing() {
let storage = InMemoryNodeStorage::<32>::new();
let phantom_hash = ValueDigest::<32>::new(b"never_inserted");
let env = make_envelope::<32>(&phantom_hash, 100);
let got = unwrap_value::<32, _>(&env, &storage);
assert_eq!(got, env);
}
}