use crate::{Result, RevitFile};
pub const HEADER_SIZE: usize = 44;
#[derive(Debug, Clone)]
pub struct PartitionHeader {
pub declared_count_plus_one: u32,
pub reserved_zero: u32,
pub size_block: [u8; 12],
pub trailer_u32: [u32; 4],
}
#[derive(Debug, Clone)]
pub struct PartitionChunk {
pub raw_offset: usize,
pub raw_len: usize,
}
pub fn parse_header(raw: &[u8]) -> Option<PartitionHeader> {
if raw.len() < HEADER_SIZE {
return None;
}
let declared_count_plus_one = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
let reserved_zero = u32::from_le_bytes([raw[4], raw[5], raw[6], raw[7]]);
let mut size_block = [0u8; 12];
size_block.copy_from_slice(&raw[8..20]);
let mut trailer_u32 = [0u32; 4];
for (i, slot) in trailer_u32.iter_mut().enumerate() {
let base = 0x14 + i * 4;
*slot = u32::from_le_bytes([raw[base], raw[base + 1], raw[base + 2], raw[base + 3]]);
}
Some(PartitionHeader {
declared_count_plus_one,
reserved_zero,
size_block,
trailer_u32,
})
}
pub fn chunks_from_stream(rf: &mut RevitFile) -> Result<Vec<PartitionChunk>> {
let name = rf
.partition_stream_name()
.ok_or_else(|| crate::Error::StreamNotFound("no Partitions/NN stream".into()))?;
let raw = rf.read_stream(&name)?;
Ok(find_chunks(&raw))
}
pub fn find_chunks(raw: &[u8]) -> Vec<PartitionChunk> {
let mut positions: Vec<usize> = Vec::new();
for i in 0..raw.len().saturating_sub(2) {
if raw[i] == 0x1f && raw[i + 1] == 0x8b && raw[i + 2] == 0x08 {
positions.push(i);
}
}
let mut chunks = Vec::with_capacity(positions.len());
for (n, &start) in positions.iter().enumerate() {
let end = positions.get(n + 1).copied().unwrap_or(raw.len());
chunks.push(PartitionChunk {
raw_offset: start,
raw_len: end - start,
});
}
chunks
}
pub fn header_bytes(raw: &[u8]) -> &[u8] {
for i in 0..raw.len().saturating_sub(2) {
if raw[i] == 0x1f && raw[i + 1] == 0x8b && raw[i + 2] == 0x08 {
return &raw[..i];
}
}
raw
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn splits_three_gzip_chunks() {
let mut buf = Vec::<u8>::new();
buf.extend_from_slice(&[0, 0, 0, 0]); buf.extend_from_slice(&[0x1f, 0x8b, 0x08, 0x00]);
buf.extend_from_slice(&[0xaa; 10]);
buf.extend_from_slice(&[0x1f, 0x8b, 0x08, 0x00]);
buf.extend_from_slice(&[0xbb; 5]);
buf.extend_from_slice(&[0x1f, 0x8b, 0x08, 0x00]);
buf.extend_from_slice(&[0xcc; 3]);
let chunks = find_chunks(&buf);
assert_eq!(chunks.len(), 3);
assert_eq!(chunks[0].raw_offset, 4);
assert_eq!(chunks[0].raw_len, 14);
assert_eq!(chunks[1].raw_offset, 18);
assert_eq!(chunks[1].raw_len, 9);
assert_eq!(chunks[2].raw_offset, 27);
}
#[test]
fn header_bytes_returns_prefix() {
let buf = [0u8, 0, 0, 0, 1, 2, 3, 0x1f, 0x8b, 0x08, 4, 5, 6];
let h = header_bytes(&buf);
assert_eq!(h, &[0u8, 0, 0, 0, 1, 2, 3]);
}
#[test]
fn find_chunks_detects_magic_at_last_valid_offset() {
let buf = [0u8, 0, 0, 0, 0x1f, 0x8b, 0x08]; let chunks = find_chunks(&buf);
assert_eq!(
chunks.len(),
1,
"gzip magic at the last valid starting offset must be found"
);
assert_eq!(chunks[0].raw_offset, 4);
}
#[test]
fn find_chunks_handles_tiny_inputs() {
assert_eq!(find_chunks(&[]).len(), 0);
assert_eq!(find_chunks(&[0x1f]).len(), 0);
assert_eq!(find_chunks(&[0x1f, 0x8b]).len(), 0);
assert_eq!(find_chunks(&[0x1f, 0x8b, 0x08]).len(), 1);
}
#[test]
fn parses_44_byte_header() {
let mut h = vec![0u8; 44];
h[0..4].copy_from_slice(&7u32.to_le_bytes()); h[0x14..0x18].copy_from_slice(&400u32.to_le_bytes());
h[0x20..0x24].copy_from_slice(&131060u32.to_le_bytes()); let parsed = parse_header(&h).unwrap();
assert_eq!(parsed.declared_count_plus_one, 7);
assert_eq!(parsed.reserved_zero, 0);
assert_eq!(parsed.trailer_u32[0], 400);
assert_eq!(parsed.trailer_u32[3], 131060);
}
}