use crate::encode::build_pipeline_config;
use crate::error::TensogramError;
use crate::hash;
use crate::metadata;
use crate::wire::FrameType;
use super::structure::FrameWalkResult;
use super::types::*;
enum HashCheckResult {
Verified,
Skipped,
Failed,
}
fn inline_slot_hex(buf: &[u8], frame_offset: usize) -> Option<String> {
use crate::wire::{FRAME_COMMON_FOOTER_SIZE, FRAME_HEADER_SIZE};
if frame_offset + FRAME_HEADER_SIZE > buf.len() {
return None;
}
let fh = crate::wire::FrameHeader::read_from(&buf[frame_offset..]).ok()?;
let total = usize::try_from(fh.total_length).ok()?;
if total < FRAME_COMMON_FOOTER_SIZE || frame_offset + total > buf.len() {
return None;
}
let slot_start = frame_offset + total - FRAME_COMMON_FOOTER_SIZE;
let slot = u64::from_be_bytes(buf[slot_start..slot_start + 8].try_into().ok()?);
Some(format!("{slot:016x}"))
}
fn verify_object_frame_hash(
buf: &[u8],
frame_offset: usize,
obj_idx: usize,
issues: &mut Vec<ValidationIssue>,
) -> HashCheckResult {
if frame_offset + crate::wire::FRAME_HEADER_SIZE > buf.len() {
return HashCheckResult::Skipped;
}
let fh = match crate::wire::FrameHeader::read_from(&buf[frame_offset..]) {
Ok(fh) => fh,
Err(_) => return HashCheckResult::Skipped,
};
let total = match usize::try_from(fh.total_length) {
Ok(t) => t,
Err(_) => return HashCheckResult::Skipped,
};
if frame_offset + total > buf.len() {
return HashCheckResult::Skipped;
}
let frame_bytes = &buf[frame_offset..frame_offset + total];
match hash::verify_frame_hash(frame_bytes, fh.frame_type) {
Ok(()) => HashCheckResult::Verified,
Err(TensogramError::HashMismatch { expected, actual }) => {
issues.push(err(
IssueCode::HashMismatch,
ValidationLevel::Integrity,
Some(obj_idx),
Some(frame_offset),
format!("frame hash mismatch (expected {expected}, got {actual})"),
));
HashCheckResult::Failed
}
Err(e) => {
issues.push(err(
IssueCode::HashVerificationError,
ValidationLevel::Integrity,
Some(obj_idx),
Some(frame_offset),
format!("frame hash verification error: {e}"),
));
HashCheckResult::Failed
}
}
}
pub(crate) fn validate_integrity(
buf: &[u8],
walk: &FrameWalkResult<'_>,
objects: &mut [ObjectContext<'_>],
issues: &mut Vec<ValidationIssue>,
checksum_only: bool,
cache_decoded: bool,
) -> bool {
let mut all_verified = true;
let mut any_checked = false;
let mut aggregate_hashes: Option<Vec<String>> = None;
for (ft, payload) in &walk.meta_frames {
if !matches!(ft, FrameType::HeaderHash | FrameType::FooterHash) {
continue;
}
match metadata::cbor_to_hash_frame(payload) {
Ok(hf) => {
if hash::HashAlgorithm::parse(&hf.algorithm).is_err() {
issues.push(warn(
IssueCode::UnknownHashAlgorithm,
ValidationLevel::Integrity,
None,
None,
format!(
"unknown hash algorithm '{}' in HashFrame, aggregate \
entries cannot be verified (inline slots still checked)",
hf.algorithm
),
));
} else {
match &aggregate_hashes {
None => aggregate_hashes = Some(hf.hashes.clone()),
Some(first) if first != &hf.hashes => {
all_verified = false;
issues.push(err(
IssueCode::HashMismatch,
ValidationLevel::Integrity,
None,
None,
"HeaderHash and FooterHash aggregate frames \
disagree — wire-format contract requires \
they carry identical hash lists"
.to_string(),
));
}
Some(_) => {} }
}
}
Err(e) => {
all_verified = false;
issues.push(err(
IssueCode::HashFrameCborParseFailed,
ValidationLevel::Integrity,
None,
None,
format!("failed to parse hash frame CBOR: {e}"),
));
}
}
}
let has_hashes = crate::wire::Preamble::read_from(buf)
.map(|p| p.flags.has(crate::wire::MessageFlags::HASHES_PRESENT))
.unwrap_or(false);
for (i, obj) in objects.iter_mut().enumerate() {
if obj.descriptor.is_none() && !obj.descriptor_failed {
match metadata::cbor_to_object_descriptor(obj.cbor_bytes) {
Ok(d) => {
obj.descriptor = Some(d);
}
Err(e) => {
obj.descriptor_failed = true;
issues.push(err(
IssueCode::HashVerificationError,
ValidationLevel::Integrity,
Some(i),
None,
format!("failed to parse descriptor: {e}"),
));
}
}
}
let result = if !has_hashes {
issues.push(warn(
IssueCode::NoHashAvailable,
ValidationLevel::Integrity,
Some(i),
None,
"HASHES_PRESENT flag is clear — message was encoded \
without hashes; cannot verify integrity"
.to_string(),
));
all_verified = false;
HashCheckResult::Skipped
} else {
match verify_object_frame_hash(buf, obj.frame_offset, i, issues) {
HashCheckResult::Verified => {
any_checked = true;
HashCheckResult::Verified
}
other => other,
}
};
if !matches!(result, HashCheckResult::Verified) {
all_verified = false;
}
if matches!(result, HashCheckResult::Verified)
&& let Some(ref hashes) = aggregate_hashes
&& let Some(expected_hex) = hashes.get(i)
&& let Some(inline_hex) = inline_slot_hex(buf, obj.frame_offset)
&& expected_hex != &inline_hex
{
all_verified = false;
issues.push(err(
IssueCode::HashMismatch,
ValidationLevel::Integrity,
Some(i),
Some(obj.frame_offset),
format!(
"HashFrame aggregate[{i}] = {expected_hex} disagrees with \
frame {i} inline slot = {inline_hex}"
),
));
}
if !checksum_only
&& let Some(ref desc) = obj.descriptor
&& (desc.compression != "none" || desc.encoding != "none" || desc.filter != "none")
{
let shape_product = desc
.shape
.iter()
.try_fold(1u64, |acc, &x| acc.checked_mul(x));
let num_elements = match shape_product {
Some(product) => match usize::try_from(product) {
Ok(n) => Some(n),
Err(_) => {
obj.decode_state = DecodeState::DecodeFailed;
issues.push(err(
IssueCode::PipelineConfigFailed,
ValidationLevel::Integrity,
Some(i),
Some(obj.frame_offset),
format!("shape product {} does not fit in usize", product),
));
None
}
},
None => {
obj.decode_state = DecodeState::DecodeFailed;
None
}
};
if let Some(num_elements) = num_elements {
match build_pipeline_config(desc, num_elements, desc.dtype) {
Ok(config) => {
match tensogram_encodings::pipeline::decode_pipeline(
obj.payload,
&config,
false,
) {
Ok(decoded) => {
if cache_decoded {
obj.decode_state = DecodeState::Decoded(decoded);
}
}
Err(e) => {
obj.decode_state = DecodeState::DecodeFailed;
issues.push(err(
IssueCode::DecodePipelineFailed,
ValidationLevel::Integrity,
Some(i),
Some(obj.frame_offset),
format!("decode pipeline failed: {e}"),
));
}
}
}
Err(e) => {
obj.decode_state = DecodeState::DecodeFailed;
issues.push(err(
IssueCode::PipelineConfigFailed,
ValidationLevel::Integrity,
Some(i),
Some(obj.frame_offset),
format!("cannot build pipeline config: {e}"),
));
}
}
}
}
}
any_checked && all_verified
}