use std::fmt;
use std::ops::Range;
use std::sync::OnceLock;
use super::ContextResponse;
#[path = "binary_validation.rs"]
mod validation;
pub const PACK_BINARY_SCHEMA_V1: &str = "ee.pack.bin.v1";
pub const PACK_BINARY_MAGIC: [u8; 4] = *b"EEPK";
pub const PACK_BINARY_VERSION_V1: u16 = 1;
pub const PACK_BINARY_HEADER_LEN: usize = 56;
pub const PACK_BINARY_ITEM_TABLE_ENTRY_LEN: usize = 16;
pub const PACK_BINARY_TRAILER_LEN: usize = 20;
pub const PACK_BINARY_FLAG_EXPLAIN_INCLUDED: u16 = 1;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PackBinaryError {
MagicMismatch {
found: [u8; 4],
},
VersionTooNew {
found: u16,
supported: u16,
},
Truncated {
needed: usize,
actual: usize,
},
ItemCountTooLarge {
count: u64,
},
TotalBytesMismatch {
declared: u64,
actual: usize,
},
InvalidOffset {
index: usize,
offset: u64,
len: u32,
total: usize,
},
ContentHashMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
InvalidItemContent {
index: Option<usize>,
reason: &'static str,
},
NonUtf8Json,
}
impl PackBinaryError {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::MagicMismatch { .. } => "pack_bin_magic_mismatch",
Self::VersionTooNew { .. } => "pack_bin_version_too_new",
Self::Truncated { .. } => "pack_bin_truncated",
Self::ItemCountTooLarge { .. } => "pack_bin_item_count_too_large",
Self::TotalBytesMismatch { .. } => "pack_bin_total_bytes_mismatch",
Self::InvalidOffset { .. } => "pack_bin_invalid_offset",
Self::ContentHashMismatch { .. } | Self::InvalidItemContent { .. } => {
"pack_bin_content_hash_mismatch"
}
Self::NonUtf8Json => "pack_bin_json_non_utf8",
}
}
}
impl fmt::Display for PackBinaryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MagicMismatch { found } => write!(
formatter,
"binary pack magic mismatch: expected EEPK, found {}",
hex_bytes(found)
),
Self::VersionTooNew { found, supported } => write!(
formatter,
"binary pack version {found} is newer than supported version {supported}"
),
Self::Truncated { needed, actual } => {
write!(
formatter,
"binary pack is truncated: needed at least {needed} bytes, found {actual}"
)
}
Self::ItemCountTooLarge { count } => {
write!(
formatter,
"binary pack item count {count} does not fit this host"
)
}
Self::TotalBytesMismatch { declared, actual } => write!(
formatter,
"binary pack total_bytes mismatch: header declares {declared}, file has {actual}"
),
Self::InvalidOffset {
index,
offset,
len,
total,
} => write!(
formatter,
"binary pack item offset {index} has invalid frame geometry: offset={offset}, len={len}, total={total}"
),
Self::ContentHashMismatch { .. } => {
formatter.write_str("binary pack content_hash does not match canonical JSON")
}
Self::InvalidItemContent { index, reason } => match index {
Some(index) => write!(
formatter,
"binary pack item {index} failed verification: {reason}"
),
None => write!(
formatter,
"binary pack item projection failed verification: {reason}"
),
},
Self::NonUtf8Json => formatter.write_str("binary pack canonical JSON is not UTF-8"),
}
}
}
impl std::error::Error for PackBinaryError {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PackBinaryHeader {
pub version: u16,
pub flags: u16,
pub item_count: usize,
pub total_bytes: usize,
pub content_hash: [u8; 32],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct PackBinaryItemEntry {
offset: usize,
len: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PackBinaryEvent {
pub schema: &'static str,
pub kind: &'static str,
pub operation: &'static str,
pub bytes: usize,
pub items: usize,
pub content_hash: String,
pub elapsed_us: u64,
pub endianness_swap_required: bool,
}
impl PackBinaryEvent {
#[must_use]
pub fn to_json_line(&self) -> String {
format!(
"{{\"schema\":\"{}\",\"kind\":\"{}\",\"operation\":\"{}\",\"bytes\":{},\"items\":{},\"content_hash\":\"{}\",\"elapsed_us\":{},\"endianness_swap_required\":{}}}",
self.schema,
self.kind,
self.operation,
self.bytes,
self.items,
self.content_hash,
self.elapsed_us,
self.endianness_swap_required
)
}
}
#[derive(Clone, Debug)]
pub struct PackBinaryView<'a> {
bytes: &'a [u8],
header: PackBinaryHeader,
entries: Vec<PackBinaryItemEntry>,
canonical_json_range: Range<usize>,
item_validation: OnceLock<Result<(), PackBinaryError>>,
}
impl<'a> PackBinaryView<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, PackBinaryError> {
ensure_len(bytes, PACK_BINARY_HEADER_LEN + PACK_BINARY_TRAILER_LEN)?;
let found_magic = read_magic(bytes);
if found_magic != PACK_BINARY_MAGIC {
return Err(PackBinaryError::MagicMismatch { found: found_magic });
}
let version = read_u16(bytes, 4)?;
if version > PACK_BINARY_VERSION_V1 {
return Err(PackBinaryError::VersionTooNew {
found: version,
supported: PACK_BINARY_VERSION_V1,
});
}
let flags = read_u16(bytes, 6)?;
let raw_item_count = read_u64(bytes, 8)?;
let item_count =
usize::try_from(raw_item_count).map_err(|_| PackBinaryError::ItemCountTooLarge {
count: raw_item_count,
})?;
let declared_total = read_u64(bytes, 16)?;
let declared_total_usize =
usize::try_from(declared_total).map_err(|_| PackBinaryError::TotalBytesMismatch {
declared: declared_total,
actual: bytes.len(),
})?;
if declared_total_usize != bytes.len() {
return Err(PackBinaryError::TotalBytesMismatch {
declared: declared_total,
actual: bytes.len(),
});
}
let table_len = item_count
.checked_mul(PACK_BINARY_ITEM_TABLE_ENTRY_LEN)
.ok_or(PackBinaryError::ItemCountTooLarge {
count: raw_item_count,
})?;
let blob_start = PACK_BINARY_HEADER_LEN.checked_add(table_len).ok_or(
PackBinaryError::ItemCountTooLarge {
count: raw_item_count,
},
)?;
let minimum_len = blob_start.checked_add(PACK_BINARY_TRAILER_LEN).ok_or(
PackBinaryError::ItemCountTooLarge {
count: raw_item_count,
},
)?;
ensure_len(bytes, minimum_len)?;
let trailer_offset = bytes.len() - PACK_BINARY_TRAILER_LEN;
let canonical_json_offset = read_u64(bytes, trailer_offset)?;
let canonical_json_len = read_u64(bytes, trailer_offset + 8)?;
let canonical_json_start =
usize::try_from(canonical_json_offset).map_err(|_| PackBinaryError::InvalidOffset {
index: item_count,
offset: canonical_json_offset,
len: 0,
total: bytes.len(),
})?;
let canonical_json_len_usize =
usize::try_from(canonical_json_len).map_err(|_| PackBinaryError::InvalidOffset {
index: item_count,
offset: canonical_json_offset,
len: u32::MAX,
total: bytes.len(),
})?;
let canonical_json_end = checked_range_end(
item_count,
canonical_json_start,
canonical_json_len_usize,
bytes.len(),
)?;
if canonical_json_start < blob_start || canonical_json_end != trailer_offset {
return Err(PackBinaryError::InvalidOffset {
index: item_count,
offset: canonical_json_offset,
len: u32::try_from(canonical_json_len_usize).unwrap_or(u32::MAX),
total: bytes.len(),
});
}
let mut content_hash = [0_u8; 32];
content_hash.copy_from_slice(&bytes[24..56]);
let actual_hash =
*blake3::hash(&bytes[canonical_json_start..canonical_json_end]).as_bytes();
if content_hash != actual_hash {
return Err(PackBinaryError::ContentHashMismatch {
expected: content_hash,
actual: actual_hash,
});
}
let mut entries = Vec::with_capacity(item_count);
let mut next_item_offset = blob_start;
for index in 0..item_count {
let entry_offset = PACK_BINARY_HEADER_LEN + index * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let raw_offset = read_u64(bytes, entry_offset)?;
let raw_len = read_u32(bytes, entry_offset + 8)?;
let offset =
usize::try_from(raw_offset).map_err(|_| PackBinaryError::InvalidOffset {
index,
offset: raw_offset,
len: raw_len,
total: bytes.len(),
})?;
let len = usize::try_from(raw_len).map_err(|_| PackBinaryError::InvalidOffset {
index,
offset: raw_offset,
len: raw_len,
total: bytes.len(),
})?;
let end = checked_range_end(index, offset, len, bytes.len())?;
if offset != next_item_offset || end > canonical_json_start {
return Err(PackBinaryError::InvalidOffset {
index,
offset: raw_offset,
len: raw_len,
total: bytes.len(),
});
}
next_item_offset = end;
entries.push(PackBinaryItemEntry { offset, len });
}
if next_item_offset != canonical_json_start {
return Err(PackBinaryError::InvalidOffset {
index: item_count,
offset: next_item_offset as u64,
len: 0,
total: bytes.len(),
});
}
Ok(Self {
bytes,
header: PackBinaryHeader {
version,
flags,
item_count,
total_bytes: declared_total_usize,
content_hash,
},
entries,
canonical_json_range: canonical_json_start..canonical_json_end,
item_validation: OnceLock::new(),
})
}
pub fn parse_verified(bytes: &'a [u8]) -> Result<Self, PackBinaryError> {
let view = Self::parse(bytes)?;
view.validate_item_contents()?;
Ok(view)
}
pub fn validate_item_contents(&self) -> Result<(), PackBinaryError> {
self.item_validation
.get_or_init(|| validation::validate(self))
.clone()
}
#[must_use]
pub const fn schema(&self) -> &'static str {
PACK_BINARY_SCHEMA_V1
}
#[must_use]
pub const fn header(&self) -> PackBinaryHeader {
self.header
}
#[must_use]
pub const fn item_count(&self) -> usize {
self.header.item_count
}
#[must_use]
pub fn content_hash_hex(&self) -> String {
format!("blake3:{}", hex32(&self.header.content_hash))
}
pub fn item_slice(&self, index: usize) -> Result<&'a [u8], PackBinaryError> {
let Some(entry) = self.entries.get(index) else {
return Err(PackBinaryError::InvalidOffset {
index,
offset: 0,
len: 0,
total: self.bytes.len(),
});
};
self.validate_item_contents()?;
Ok(&self.bytes[entry.offset..entry.offset + entry.len])
}
#[must_use]
pub fn canonical_json_bytes(&self) -> &'a [u8] {
&self.bytes[self.canonical_json_range.clone()]
}
pub fn canonical_json(&self) -> Result<&'a str, PackBinaryError> {
std::str::from_utf8(self.canonical_json_bytes()).map_err(|_| PackBinaryError::NonUtf8Json)
}
#[must_use]
pub fn to_json_bytes(&self) -> &'a [u8] {
self.canonical_json_bytes()
}
#[must_use]
pub fn event(&self, operation: &'static str, elapsed_us: u64) -> PackBinaryEvent {
PackBinaryEvent {
schema: "ee.test_event.v1",
kind: "pack_binary",
operation,
bytes: self.bytes.len(),
items: self.item_count(),
content_hash: self.content_hash_hex(),
elapsed_us,
endianness_swap_required: false,
}
}
}
#[must_use]
pub fn serialize_context_response_binary(
response: &ContextResponse,
canonical_json: &str,
) -> Vec<u8> {
let item_contents = response
.data
.pack
.items
.iter()
.map(|item| item.content.as_bytes())
.chain(
response
.data
.pack
.rule_items
.iter()
.map(|item| item.content.as_bytes()),
)
.chain(
response
.data
.pack
.evidence_items
.iter()
.map(|item| item.content.as_bytes()),
)
.collect::<Vec<_>>();
let flags = if response.data.pack_dna.is_some() {
PACK_BINARY_FLAG_EXPLAIN_INCLUDED
} else {
0
};
serialize_pack_binary(canonical_json, &item_contents, flags)
}
#[must_use]
pub fn serialize_pack_binary(canonical_json: &str, item_contents: &[&[u8]], flags: u16) -> Vec<u8> {
let canonical_json_bytes = canonical_json.as_bytes();
let content_hash = blake3::hash(canonical_json_bytes);
let table_len = item_contents.len() * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let blob_start = PACK_BINARY_HEADER_LEN + table_len;
let item_blob_bytes = item_contents.iter().map(|item| item.len()).sum::<usize>();
let canonical_json_offset = blob_start + item_blob_bytes;
let total_bytes = canonical_json_offset + canonical_json_bytes.len() + PACK_BINARY_TRAILER_LEN;
let mut frame = Vec::with_capacity(total_bytes);
frame.extend_from_slice(&PACK_BINARY_MAGIC);
frame.extend_from_slice(&PACK_BINARY_VERSION_V1.to_le_bytes());
frame.extend_from_slice(&flags.to_le_bytes());
frame.extend_from_slice(&(item_contents.len() as u64).to_le_bytes());
frame.extend_from_slice(&(total_bytes as u64).to_le_bytes());
frame.extend_from_slice(content_hash.as_bytes());
let mut next_offset = blob_start;
for item in item_contents {
frame.extend_from_slice(&(next_offset as u64).to_le_bytes());
let len_field = u32::try_from(item.len()).unwrap_or(u32::MAX);
frame.extend_from_slice(&len_field.to_le_bytes());
frame.extend_from_slice(&0_u32.to_le_bytes());
next_offset += item.len();
}
for item in item_contents {
frame.extend_from_slice(item);
}
frame.extend_from_slice(canonical_json_bytes);
frame.extend_from_slice(&(canonical_json_offset as u64).to_le_bytes());
frame.extend_from_slice(&(canonical_json_bytes.len() as u64).to_le_bytes());
frame.extend_from_slice(&0_u32.to_le_bytes());
debug_assert_eq!(frame.len(), total_bytes);
frame
}
fn ensure_len(bytes: &[u8], needed: usize) -> Result<(), PackBinaryError> {
if bytes.len() < needed {
Err(PackBinaryError::Truncated {
needed,
actual: bytes.len(),
})
} else {
Ok(())
}
}
fn checked_range_end(
index: usize,
offset: usize,
len: usize,
total: usize,
) -> Result<usize, PackBinaryError> {
offset
.checked_add(len)
.filter(|end| *end <= total)
.ok_or(PackBinaryError::InvalidOffset {
index,
offset: offset as u64,
len: u32::try_from(len).unwrap_or(u32::MAX),
total,
})
}
fn read_magic(bytes: &[u8]) -> [u8; 4] {
[bytes[0], bytes[1], bytes[2], bytes[3]]
}
fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, PackBinaryError> {
ensure_len(bytes, offset + 2)?;
Ok(u16::from_le_bytes([bytes[offset], bytes[offset + 1]]))
}
fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, PackBinaryError> {
ensure_len(bytes, offset + 4)?;
Ok(u32::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
]))
}
fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, PackBinaryError> {
ensure_len(bytes, offset + 8)?;
Ok(u64::from_le_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]))
}
fn hex32(bytes: &[u8; 32]) -> String {
hex_bytes(bytes)
}
fn hex_bytes(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for byte in bytes {
out.push(HEX[(byte >> 4) as usize] as char);
out.push(HEX[(byte & 0x0f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::{
PACK_BINARY_HEADER_LEN, PACK_BINARY_ITEM_TABLE_ENTRY_LEN, PACK_BINARY_TRAILER_LEN,
serialize_pack_binary,
};
fn fixture_json() -> &'static str {
r#"{"schema":"ee.response.v2","success":true,"data":{"pack":{"items":[]}}}"#
}
fn fixture_items() -> [&'static [u8]; 3] {
[b"alpha", b"bravo", b"charlie"]
}
#[test]
fn trailer_layout_matches_canonical_offset_len_and_reserved_zero() {
let json = fixture_json();
let items = fixture_items();
let item_slices: Vec<&[u8]> = items.iter().copied().collect();
let frame = serialize_pack_binary(json, &item_slices, 0);
let trailer_start = frame.len() - PACK_BINARY_TRAILER_LEN;
let trailer = &frame[trailer_start..];
assert_eq!(trailer.len(), PACK_BINARY_TRAILER_LEN);
let raw_offset = u64::from_le_bytes(trailer[0..8].try_into().expect("offset bytes"));
let raw_len = u64::from_le_bytes(trailer[8..16].try_into().expect("len bytes"));
let raw_reserved = u32::from_le_bytes(trailer[16..20].try_into().expect("reserved bytes"));
let table_len = items.len() * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let items_blob_bytes: usize = items.iter().map(|item| item.len()).sum();
let expected_offset = (PACK_BINARY_HEADER_LEN + table_len + items_blob_bytes) as u64;
assert_eq!(
raw_offset, expected_offset,
"canonical_json_offset position"
);
assert_eq!(
raw_len,
json.as_bytes().len() as u64,
"canonical_json_len value"
);
assert_eq!(raw_reserved, 0, "trailer reserved u32 must remain 0");
}
#[test]
fn item_table_reserved_u32_is_zero_for_every_entry() {
let items = fixture_items();
let item_slices: Vec<&[u8]> = items.iter().copied().collect();
let frame = serialize_pack_binary(fixture_json(), &item_slices, 0);
for (index, _) in items.iter().enumerate() {
let entry_start = PACK_BINARY_HEADER_LEN + index * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let reserved = u32::from_le_bytes(
frame[entry_start + 12..entry_start + 16]
.try_into()
.expect("entry reserved bytes"),
);
assert_eq!(
reserved, 0,
"item table entry {index} reserved u32 must be 0"
);
}
}
#[test]
fn item_table_entry_offsets_pack_contiguously_no_gaps_or_overlap() {
let items = fixture_items();
let item_slices: Vec<&[u8]> = items.iter().copied().collect();
let frame = serialize_pack_binary(fixture_json(), &item_slices, 0);
let table_len = items.len() * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let expected_first_offset = (PACK_BINARY_HEADER_LEN + table_len) as u64;
let mut entries: Vec<(u64, u32)> = Vec::with_capacity(items.len());
for index in 0..items.len() {
let entry_start = PACK_BINARY_HEADER_LEN + index * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let offset = u64::from_le_bytes(
frame[entry_start..entry_start + 8]
.try_into()
.expect("entry offset bytes"),
);
let len = u32::from_le_bytes(
frame[entry_start + 8..entry_start + 12]
.try_into()
.expect("entry len bytes"),
);
entries.push((offset, len));
}
assert_eq!(
entries[0].0, expected_first_offset,
"first item offset must equal end of item-table"
);
for window in entries.windows(2) {
let (prev_offset, prev_len) = window[0];
let (next_offset, _) = window[1];
assert_eq!(
next_offset,
prev_offset + prev_len as u64,
"adjacent item offsets must be contiguous (no gap, no overlap)"
);
}
}
#[test]
fn item_table_to_canonical_json_blob_boundary_is_exact() {
let items = fixture_items();
let item_slices: Vec<&[u8]> = items.iter().copied().collect();
let frame = serialize_pack_binary(fixture_json(), &item_slices, 0);
let last_index = items.len() - 1;
let last_entry_start =
PACK_BINARY_HEADER_LEN + last_index * PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let last_offset = u64::from_le_bytes(
frame[last_entry_start..last_entry_start + 8]
.try_into()
.expect("last offset bytes"),
);
let last_len = u32::from_le_bytes(
frame[last_entry_start + 8..last_entry_start + 12]
.try_into()
.expect("last len bytes"),
);
let items_blob_end = last_offset + last_len as u64;
let trailer_start = frame.len() - PACK_BINARY_TRAILER_LEN;
let canonical_json_offset = u64::from_le_bytes(
frame[trailer_start..trailer_start + 8]
.try_into()
.expect("canonical_json_offset bytes"),
);
assert_eq!(
items_blob_end, canonical_json_offset,
"items blob must end exactly where canonical_json begins"
);
}
fn response_with_evidence(memory_count: u32, evidence_count: u32) -> super::ContextResponse {
use crate::models::{
EvidenceId, LineSpan, MemoryId, ProvenanceUri, SessionId, TrustClass, UnitScore,
};
use crate::pack::{
ContextRequest, ContextResponse, PackCandidate, PackCandidateInput, PackEvidenceItem,
PackProvenance, PackSection, PackTrustSignal, TokenBudget, assemble_draft,
};
let candidates: Vec<_> = (0..memory_count)
.map(|index| {
let id = MemoryId::from_uuid(uuid::Uuid::from_u128(0x1000 + u128::from(index)));
PackCandidate::new(PackCandidateInput {
memory_id: id,
section: PackSection::ProceduralRules,
content: format!("Memory {index}: run the release checks."),
estimated_tokens: 8,
relevance: UnitScore::parse(0.9).expect("unit relevance"),
utility: UnitScore::parse(0.5).expect("unit utility"),
provenance: vec![
PackProvenance::new(ProvenanceUri::EeMemory(id), "binary fixture")
.expect("memory provenance"),
],
why: "release procedure".to_string(),
})
.expect("memory candidate")
})
.collect();
let mut draft = assemble_draft(
"release checks",
TokenBudget::new(128).expect("token budget"),
candidates,
)
.expect("assembled draft");
assert_eq!(draft.items.len(), memory_count as usize);
for index in 0..evidence_count {
let session_id =
SessionId::from_uuid(uuid::Uuid::from_u128(0x2000 + u128::from(index))).to_string();
draft.evidence_items.push(PackEvidenceItem {
rank: memory_count + index + 1,
evidence_id: EvidenceId::from_uuid(uuid::Uuid::from_u128(
0x3000 + u128::from(index),
))
.to_string(),
entity_revision: format!("blake3:{}", "a".repeat(64)),
session_id: session_id.clone(),
start_line: 3,
end_line: 4,
section: PackSection::Evidence,
content: format!("Evidence {index}: café deployment\npassed the checks."),
estimated_tokens: 8,
relevance: UnitScore::parse(0.8).expect("unit relevance"),
utility: UnitScore::parse(0.5).expect("neutral evidence utility"),
provenance: vec![
PackProvenance::new(
ProvenanceUri::CassSession {
session: session_id,
span: Some(LineSpan::range(3, 4).expect("line range")),
},
"CASS evidence",
)
.expect("evidence provenance"),
],
why: "matched imported session".to_string(),
trust: PackTrustSignal {
class: TrustClass::CassEvidence,
subclass: None,
},
});
draft.used_tokens += 8;
}
ContextResponse::new(
ContextRequest::from_query("release checks").expect("request"),
draft,
Vec::new(),
)
.expect("context response")
}
fn assert_binary_matches_batch(memory_count: u32, evidence_count: u32) {
let response = response_with_evidence(memory_count, evidence_count);
let canonical_json = crate::output::render_context_response_json(&response);
let batch: serde_json::Value = serde_json::from_str(&canonical_json).expect("batch JSON");
let batch_items = batch["data"]["pack"]["items"]
.as_array()
.expect("batch items");
assert_eq!(batch_items.len(), (memory_count + evidence_count) as usize);
let frame = super::serialize_context_response_binary(&response, &canonical_json);
let view = super::PackBinaryView::parse(&frame).expect("valid binary pack");
assert_eq!(view.item_count(), batch_items.len());
assert_eq!(view.canonical_json().expect("UTF-8 JSON"), canonical_json);
assert_eq!(
view.header().content_hash,
*blake3::hash(canonical_json.as_bytes()).as_bytes()
);
for (index, item) in batch_items.iter().enumerate() {
assert_eq!(
view.item_slice(index).expect("binary item"),
item["content"].as_str().expect("batch content").as_bytes(),
"binary item {index} must match its canonical JSON item"
);
if index >= memory_count as usize {
let evidence = &response.data.pack.evidence_items[index - memory_count as usize];
assert!(item.get("memoryId").is_none());
assert_eq!(item["entityKind"], "evidence_span");
assert_eq!(item["evidenceSpanId"], evidence.evidence_id);
assert_eq!(item["entityRevision"], evidence.entity_revision);
assert_eq!(item["sessionId"], evidence.session_id);
}
}
assert!(view.item_slice(batch_items.len()).is_err());
assert_eq!(
frame,
super::serialize_context_response_binary(&response, &canonical_json),
"mixed-entity binary serialization must be deterministic"
);
}
#[test]
fn binary_context_preserves_native_evidence_after_memory_items() {
assert_binary_matches_batch(1, 2);
}
#[test]
fn binary_context_preserves_evidence_only_packs() {
assert_binary_matches_batch(0, 2);
}
#[test]
fn binary_context_preserves_memory_only_and_empty_packs() {
assert_binary_matches_batch(1, 0);
assert_binary_matches_batch(0, 0);
}
}
#[cfg(test)]
mod geometry_tests {
use super::*;
fn frame() -> Vec<u8> {
serialize_pack_binary("{}", &[b"alpha", b"bravo"], 0)
}
fn assert_invalid_offset(bytes: &[u8]) {
assert!(matches!(
PackBinaryView::parse(bytes),
Err(PackBinaryError::InvalidOffset { .. })
));
}
#[test]
fn rejects_aliasing_and_reordered_item_ranges() {
let mut bytes = frame();
let second = PACK_BINARY_HEADER_LEN + PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
let first_offset = read_u64(&bytes, PACK_BINARY_HEADER_LEN).expect("first offset");
bytes[second..second + 8].copy_from_slice(&first_offset.to_le_bytes());
assert_invalid_offset(&bytes);
}
#[test]
fn rejects_unreferenced_bytes_between_items() {
let mut bytes = frame();
let length = PACK_BINARY_HEADER_LEN + 8;
bytes[length..length + 4].copy_from_slice(&4_u32.to_le_bytes());
assert_invalid_offset(&bytes);
}
#[test]
fn rejects_unreferenced_bytes_before_canonical_json() {
let mut bytes = frame();
let length = PACK_BINARY_HEADER_LEN + PACK_BINARY_ITEM_TABLE_ENTRY_LEN + 8;
bytes[length..length + 4].copy_from_slice(&4_u32.to_le_bytes());
assert_invalid_offset(&bytes);
}
#[test]
fn rejects_unreferenced_bytes_before_trailer_even_with_valid_json_hash() {
let mut bytes = frame();
let trailer = bytes.len() - PACK_BINARY_TRAILER_LEN;
bytes.insert(trailer, 0);
let total = bytes.len() as u64;
bytes[16..24].copy_from_slice(&total.to_le_bytes());
assert_invalid_offset(&bytes);
}
#[test]
fn trailer_size_addition_cannot_overflow_after_valid_table_arithmetic() {
let mut bytes = frame();
let count = (usize::MAX - PACK_BINARY_HEADER_LEN) / PACK_BINARY_ITEM_TABLE_ENTRY_LEN;
bytes[8..16].copy_from_slice(&(count as u64).to_le_bytes());
assert!(matches!(
PackBinaryView::parse(&bytes),
Err(PackBinaryError::ItemCountTooLarge { .. })
));
}
#[test]
fn contiguous_empty_items_and_empty_packs_remain_valid() {
for items in [&[][..], &[&b""[..], &b"alpha"[..], &b""[..]][..]] {
let json = if items.is_empty() {
r#"{"data":{"pack":{"items":[]}}}"#
} else {
r#"{"data":{"pack":{"items":[{"content":""},{"content":"alpha"},{"content":""}]}}}"#
};
let bytes = serialize_pack_binary(json, items, 0);
let view = PackBinaryView::parse_verified(&bytes).expect("canonical pack");
assert_eq!(view.item_count(), items.len());
for (index, expected) in items.iter().enumerate() {
assert_eq!(view.item_slice(index).expect("valid item"), *expected);
}
}
}
}