use crate::catalog::{CHUNK_PAYLOAD_CODEC_V1, ChunkIndexEntryV1, DatasetRecordV1};
use super::report::{VerifyFinding, err_finding, ok_finding, warn_finding};
pub const DEEP_DECODE_MAX_CHUNKS: usize = 128;
pub(crate) fn check_chunk_dataset_ids(
datasets: &[DatasetRecordV1],
chunks: &[ChunkIndexEntryV1],
) -> Option<VerifyFinding> {
let n = datasets.len() as u64;
for (i, ch) in chunks.iter().enumerate() {
if ch.dataset_id >= n {
return Some(err_finding(
"chunk_dataset_ids",
format!(
"chunk {i}: dataset_id {} >= dataset_count {n}",
ch.dataset_id
),
));
}
}
None
}
pub(crate) fn check_payload_order(chunks: &[ChunkIndexEntryV1]) -> Option<VerifyFinding> {
for w in chunks.windows(2) {
let a = &w[0];
let b = &w[1];
if a.payload_offset.saturating_add(a.stored_byte_len) != b.payload_offset {
return Some(warn_finding(
"payload_order",
"chunk payloads are not contiguous in index order (linear scan may not apply)"
.to_owned(),
));
}
}
None
}
pub(crate) fn check_duplicate_payload_offsets(
chunks: &[ChunkIndexEntryV1],
) -> Option<VerifyFinding> {
let mut seen = std::collections::BTreeSet::new();
for (i, ch) in chunks.iter().enumerate() {
if !seen.insert(ch.payload_offset) {
return Some(err_finding(
"payload_offsets",
format!("duplicate payload_offset at chunk index {i}"),
));
}
}
None
}
pub(crate) fn check_chunk_decode(
data: &[u8],
chunks: &[ChunkIndexEntryV1],
deep_decode: bool,
) -> (Vec<VerifyFinding>, bool) {
let mut findings = Vec::new();
let deep = deep_decode || chunks.len() <= DEEP_DECODE_MAX_CHUNKS;
let to_check: Vec<usize> = if deep {
(0..chunks.len()).collect()
} else {
findings.push(warn_finding(
"chunk_decode_skipped",
format!(
"decode-check skipped for chunks {}..{} (limit {DEEP_DECODE_MAX_CHUNKS}); re-run with --deep or VerifyOptions::deep_decode",
DEEP_DECODE_MAX_CHUNKS,
chunks.len()
),
));
(0..DEEP_DECODE_MAX_CHUNKS.min(chunks.len())).collect()
};
let mut decode_failures = 0_u32;
let checked = to_check.len();
for i in &to_check {
let ch = &chunks[*i];
let start = usize::try_from(ch.payload_offset).unwrap_or(usize::MAX);
let end = start.saturating_add(usize::try_from(ch.stored_byte_len).unwrap_or(usize::MAX));
if end > data.len() {
findings.push(err_finding(
"chunk_decode",
format!("chunk {i}: payload slice out of file bounds"),
));
decode_failures += 1;
continue;
}
let stored = &data[start..end];
if let Err(e) = CHUNK_PAYLOAD_CODEC_V1.decode_tile_payload(
stored,
ch.raw_byte_len,
ch.stored_byte_len,
ch.codec,
) {
findings.push(err_finding("chunk_decode", format!("chunk {i}: {e}")));
decode_failures += 1;
}
}
if decode_failures == 0 {
let detail = if deep {
Some(format!("decoded {} chunk(s)", chunks.len()))
} else {
Some(format!("decoded {checked} of {} chunk(s)", chunks.len()))
};
findings.push(ok_finding("chunk_decode", detail));
}
(findings, deep)
}