use crate::error::{Error, Result};
pub const SECTN_MAGIC: &[u8; 8] = b"NCZSECTN";
pub const BLOCK_MAGIC: &[u8; 8] = b"NCZBLOCK";
pub const INCOMPRESSIBLE_HEADER_SIZE: u64 = 0x4000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Section {
pub offset: i64,
pub size: i64,
pub crypto_type: i64,
pub crypto_key: [u8; 16],
pub crypto_counter: [u8; 16],
}
impl Section {
pub const WIRE_SIZE: usize = 64;
pub fn read(buf: &[u8]) -> Result<Section> {
if buf.len() < Self::WIRE_SIZE {
return Err(Error::Corrupt("section too short".into()));
}
let mut crypto_key = [0u8; 16];
let mut crypto_counter = [0u8; 16];
crypto_key.copy_from_slice(&buf[32..48]);
crypto_counter.copy_from_slice(&buf[48..64]);
Ok(Section {
offset: le_i64(buf, 0),
size: le_i64(buf, 8),
crypto_type: le_i64(buf, 16),
crypto_key,
crypto_counter,
})
}
pub fn write(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.offset.to_le_bytes());
out.extend_from_slice(&self.size.to_le_bytes());
out.extend_from_slice(&self.crypto_type.to_le_bytes());
out.extend_from_slice(&0i64.to_le_bytes()); out.extend_from_slice(&self.crypto_key);
out.extend_from_slice(&self.crypto_counter);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockHeader {
pub version: i8,
pub block_type: i8,
pub unused: i8,
pub block_size_exponent: i8,
pub number_of_blocks: i32,
pub decompressed_size: i64,
pub compressed_block_size_list: Vec<u32>,
}
impl BlockHeader {
pub const FIXED_SIZE: usize = 24;
pub fn read(buf: &[u8]) -> Result<BlockHeader> {
if buf.len() < Self::FIXED_SIZE {
return Err(Error::Corrupt("block header too short".into()));
}
if &buf[0..8] != BLOCK_MAGIC {
return Err(Error::BadMagic {
expected: "NCZBLOCK".into(),
found: hex::encode(&buf[0..8]),
});
}
let number_of_blocks = le_i32(buf, 12);
if number_of_blocks < 0 {
return Err(Error::Corrupt("negative block count".into()));
}
let n = number_of_blocks as usize;
let need = Self::FIXED_SIZE + n * 4;
if buf.len() < need {
return Err(Error::Corrupt("block size list truncated".into()));
}
let mut list = Vec::with_capacity(n);
for i in 0..n {
list.push(le_u32(buf, Self::FIXED_SIZE + i * 4));
}
Ok(BlockHeader {
version: buf[8] as i8,
block_type: buf[9] as i8,
unused: buf[10] as i8,
block_size_exponent: buf[11] as i8,
number_of_blocks,
decompressed_size: le_i64(buf, 16),
compressed_block_size_list: list,
})
}
pub fn write(&self, out: &mut Vec<u8>) {
out.extend_from_slice(BLOCK_MAGIC);
out.push(self.version as u8);
out.push(self.block_type as u8);
out.push(self.unused as u8);
out.push(self.block_size_exponent as u8);
out.extend_from_slice(&self.number_of_blocks.to_le_bytes());
out.extend_from_slice(&self.decompressed_size.to_le_bytes());
for &s in &self.compressed_block_size_list {
out.extend_from_slice(&s.to_le_bytes());
}
}
}
pub fn parse_header(buf: &[u8]) -> Result<(Vec<Section>, Option<BlockHeader>)> {
if buf.len() < 16 || &buf[0..8] != SECTN_MAGIC {
return Err(Error::BadMagic {
expected: "NCZSECTN".into(),
found: hex::encode(&buf[0..buf.len().min(8)]),
});
}
let section_count = le_i64(buf, 8);
if section_count < 0 {
return Err(Error::Corrupt("negative section count".into()));
}
let n = section_count as usize;
if n > (buf.len() - 16) / Section::WIRE_SIZE {
return Err(Error::Corrupt("section table truncated".into()));
}
let table_end = 16 + n * Section::WIRE_SIZE;
let mut sections = Vec::with_capacity(n);
for i in 0..n {
sections.push(Section::read(&buf[16 + i * Section::WIRE_SIZE..])?);
}
let block = if buf.len() >= table_end + 8 && &buf[table_end..table_end + 8] == BLOCK_MAGIC {
Some(BlockHeader::read(&buf[table_end..])?)
} else {
None
};
Ok((sections, block))
}
pub fn write_header(sections: &[Section], block: Option<&BlockHeader>) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(SECTN_MAGIC);
out.extend_from_slice(&(sections.len() as i64).to_le_bytes());
for s in sections {
s.write(&mut out);
}
if let Some(b) = block {
b.write(&mut out);
}
out
}
#[inline]
pub fn le_i64(b: &[u8], at: usize) -> i64 {
let mut a = [0u8; 8];
a.copy_from_slice(&b[at..at + 8]);
i64::from_le_bytes(a)
}
#[inline]
pub fn le_i32(b: &[u8], at: usize) -> i32 {
let mut a = [0u8; 4];
a.copy_from_slice(&b[at..at + 4]);
i32::from_le_bytes(a)
}
#[inline]
pub fn le_u32(b: &[u8], at: usize) -> u32 {
let mut a = [0u8; 4];
a.copy_from_slice(&b[at..at + 4]);
u32::from_le_bytes(a)
}