#![doc = include_str!("../README.md")]
use bytes::{Buf, BufMut};
use log::trace;
use strum::FromRepr;
use thiserror::Error;
pub const FILE_HEADER_BYTES_LEN: usize = 28;
pub const CHUNK_HEADER_BYTES_LEN: usize = 12;
pub const HEADER_MAGIC: u32 = 0xed26ff3a;
#[derive(Clone, Debug, Error)]
pub enum ParseError {
#[error("Header has an unknown magic value")]
UnknownMagic,
#[error("Header has an unknown version")]
UnknownVersion,
#[error("Header has an unexpected header or chunk size")]
UnexpectedSize,
#[error("Header has an unknown chunk type")]
UnknownChunkType,
}
pub type FileHeaderBytes = [u8; FILE_HEADER_BYTES_LEN];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileHeader {
pub block_size: u32,
pub blocks: u32,
pub chunks: u32,
pub checksum: u32,
}
impl FileHeader {
pub fn from_bytes(bytes: &FileHeaderBytes) -> Result<FileHeader, ParseError> {
let mut bytes = &bytes[..];
let magic = bytes.get_u32_le();
if magic != HEADER_MAGIC {
trace!("Unrecognized header magic: {:x}", magic);
return Err(ParseError::UnknownMagic);
}
let major = bytes.get_u16_le();
if major != 0x1 {
trace!("Unrecognized major versions: {:x}", major);
return Err(ParseError::UnknownVersion);
}
let minor = bytes.get_u16_le();
if minor != 0x0 {
trace!("Unrecognized minor versions: {:x}", minor);
return Err(ParseError::UnknownVersion);
}
let header_len = bytes.get_u16_le();
if FILE_HEADER_BYTES_LEN != header_len.into() {
trace!("Unexpected header size: {}", header_len);
return Err(ParseError::UnexpectedSize);
}
let chunk_header_len = bytes.get_u16_le();
if CHUNK_HEADER_BYTES_LEN != chunk_header_len.into() {
trace!("Unexpected chunk header size: {}", chunk_header_len);
return Err(ParseError::UnexpectedSize);
}
let block_size = bytes.get_u32_le();
let blocks = bytes.get_u32_le();
let chunks = bytes.get_u32_le();
let checksum = bytes.get_u32_le();
Ok(FileHeader {
block_size,
blocks,
chunks,
checksum,
})
}
pub fn to_bytes(&self) -> FileHeaderBytes {
let mut bytes = [0; FILE_HEADER_BYTES_LEN];
let mut w = &mut bytes[..];
w.put_u32_le(HEADER_MAGIC);
w.put_u16_le(0x1);
w.put_u16_le(0x0);
w.put_u16_le(FILE_HEADER_BYTES_LEN as u16);
w.put_u16_le(CHUNK_HEADER_BYTES_LEN as u16);
w.put_u32_le(self.block_size);
w.put_u32_le(self.blocks);
w.put_u32_le(self.chunks);
w.put_u32_le(self.checksum);
bytes
}
pub fn total_size(&self) -> usize {
self.blocks as usize * self.block_size as usize
}
}
#[derive(Copy, Clone, Debug, FromRepr, Eq, PartialEq)]
pub enum ChunkType {
Raw = 0xcac1,
Fill = 0xcac2,
DontCare = 0xcac3,
Crc32 = 0xcac4,
}
pub type ChunkHeaderBytes = [u8; CHUNK_HEADER_BYTES_LEN];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChunkHeader {
pub chunk_type: ChunkType,
pub chunk_size: u32,
pub total_size: u32,
}
impl ChunkHeader {
pub fn from_bytes(bytes: &ChunkHeaderBytes) -> Result<ChunkHeader, ParseError> {
let mut bytes = &bytes[..];
let chunk_type = bytes.get_u16_le();
let Some(chunk_type) = ChunkType::from_repr(chunk_type.into()) else {
trace!("Unknown chunk type: {}", chunk_type);
return Err(ParseError::UnknownChunkType);
};
bytes.advance(2);
let chunk_size = bytes.get_u32_le();
let total_size = bytes.get_u32_le();
Ok(ChunkHeader {
chunk_type,
chunk_size,
total_size,
})
}
pub fn to_bytes(&self) -> ChunkHeaderBytes {
let mut bytes = [0; CHUNK_HEADER_BYTES_LEN];
let mut w = &mut bytes[..];
w.put_u16_le(self.chunk_type as u16);
w.put_u16_le(0x0);
w.put_u32_le(self.chunk_size);
w.put_u32_le(self.total_size);
bytes
}
pub fn out_size(&self, header: &FileHeader) -> usize {
self.chunk_size as usize * header.block_size as usize
}
pub fn data_size(&self) -> usize {
(self.total_size as usize).saturating_sub(CHUNK_HEADER_BYTES_LEN)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn file_header_parse() {
let data = [
0x3au8, 0xff, 0x26, 0xed, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x10,
0x00, 0x00, 0x77, 0x39, 0x14, 0x00, 0xb1, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0xcc,
];
let h = FileHeader::from_bytes(&data).unwrap();
assert_eq!(
h,
FileHeader {
block_size: 4096,
blocks: 1325431,
chunks: 177,
checksum: 0xcc0000aa,
}
);
}
#[test]
fn file_header_roundtrip() {
let orig = FileHeader {
block_size: 4096,
blocks: 1024,
chunks: 42,
checksum: 0xabcd,
};
let b = orig.to_bytes();
let echo = FileHeader::from_bytes(&b).unwrap();
assert_eq!(orig, echo);
}
#[test]
fn chunk_header_parse() {
let data = [
0xc3u8, 0xca, 0x0, 0x0, 0x1f, 0xf1, 0xaa, 0xbb, 0x0c, 0x00, 0x00, 0x00,
];
let h = ChunkHeader::from_bytes(&data).unwrap();
assert_eq!(
h,
ChunkHeader {
chunk_type: ChunkType::DontCare,
chunk_size: 0xbbaaf11f,
total_size: CHUNK_HEADER_BYTES_LEN as u32,
}
);
}
#[test]
fn chunk_header_roundtrip() {
let orig = ChunkHeader {
chunk_type: ChunkType::Fill,
chunk_size: 8,
total_size: (CHUNK_HEADER_BYTES_LEN + 4) as u32,
};
let b = orig.to_bytes();
let echo = ChunkHeader::from_bytes(&b).unwrap();
assert_eq!(orig, echo);
}
}