use super::super::statistics::StatisticsHeader;
use nom::{number::complete::be_u32, IResult};
#[allow(dead_code)]
const METADATA_TYPE_VALIDATION: u32 = 0;
#[allow(dead_code)]
const METADATA_TYPE_COMPACTION: u32 = 1;
#[allow(dead_code)]
const METADATA_TYPE_STATS: u32 = 2;
const METADATA_TYPE_HEADER: u32 = 3;
pub fn parse_nb_format_header(input: &[u8]) -> IResult<&[u8], StatisticsHeader> {
let (input, version_type) = be_u32(input)?;
let (input, statistics_kind) = be_u32(input)?;
let (input, _reserved1) = be_u32(input)?;
let (input, data_length) = be_u32(input)?;
let (input, metadata1) = be_u32(input)?;
let (input, metadata2) = be_u32(input)?;
let (input, metadata3) = be_u32(input)?;
let (input, checksum_or_more) = be_u32(input)?;
Ok((
input,
StatisticsHeader {
version: version_type,
statistics_kind,
data_length,
metadata1,
metadata2,
metadata3,
checksum: checksum_or_more,
table_id: None,
},
))
}
pub(super) fn parse_statistics_toc_for_header_offset(input: &[u8]) -> Option<usize> {
super::super::toc_walk_metrics::record_toc_walk();
if input.len() < 8 {
tracing::debug!("Statistics.db too small for TOC: {} bytes", input.len());
return None;
}
let num_components = u32::from_be_bytes([input[0], input[1], input[2], input[3]]);
tracing::debug!("Statistics.db TOC: {} components", num_components);
if num_components > 100 {
tracing::warn!(
"Suspicious num_components={} in Statistics.db TOC (expected <=4)",
num_components
);
return None;
}
let toc_start: usize = 8;
let toc_entry_size: usize = 8;
let toc_size = (num_components as usize)
.checked_mul(toc_entry_size)
.and_then(|size| size.checked_add(toc_start))?;
if input.len() < toc_size {
tracing::debug!(
"Statistics.db too small for {} TOC entries: {} bytes (need {})",
num_components,
input.len(),
toc_size
);
return None;
}
for i in 0..num_components as usize {
let entry_offset = i
.checked_mul(toc_entry_size)
.and_then(|offset| offset.checked_add(toc_start))?;
let component_type = u32::from_be_bytes([
input[entry_offset],
input[entry_offset + 1],
input[entry_offset + 2],
input[entry_offset + 3],
]);
let component_offset = u32::from_be_bytes([
input[entry_offset + 4],
input[entry_offset + 5],
input[entry_offset + 6],
input[entry_offset + 7],
]) as usize;
tracing::debug!(
"TOC entry {}: type={} offset=0x{:x}",
i,
component_type,
component_offset
);
if component_type == METADATA_TYPE_HEADER {
tracing::debug!(
"Found HEADER component at offset 0x{:x} ({})",
component_offset,
component_offset
);
return Some(component_offset);
}
}
tracing::debug!("HEADER component not found in Statistics.db TOC");
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nb_format_header_parsing() {
let test_data = vec![
0x00, 0x00, 0x00, 0x04, 0x26, 0x29, 0x1b, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x14, 0xd4, ];
let result = parse_nb_format_header(&test_data);
assert!(result.is_ok());
let (_, header) = result.unwrap();
assert_eq!(header.version, 4);
assert_eq!(header.statistics_kind, 0x2629_1b05);
assert_eq!(header.data_length, 44);
assert_eq!(header.metadata1, 1);
assert_eq!(header.metadata2, 101);
assert_eq!(header.metadata3, 2);
assert_eq!(header.checksum, 0x14d4);
}
}