use crate::{Error, Result};
pub const CHUNK_MAGIC: [u8; 4] = [0x4F, 0x58, 0x49, 0x53];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ChunkType {
Data = 0,
End = 1,
Metadata = 2,
}
impl ChunkType {
fn from_byte(b: u8) -> Option<Self> {
match b {
0 => Some(ChunkType::Data),
1 => Some(ChunkType::End),
2 => Some(ChunkType::Metadata),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkHeader {
pub chunk_type: ChunkType,
pub payload_len: u32,
pub item_count: u32,
}
impl ChunkHeader {
pub const SIZE: usize = 13;
#[inline]
pub fn data(payload_len: u32, item_count: u32) -> Self {
Self {
chunk_type: ChunkType::Data,
payload_len,
item_count,
}
}
#[inline]
pub fn end() -> Self {
Self {
chunk_type: ChunkType::End,
payload_len: 0,
item_count: 0,
}
}
#[inline]
pub fn metadata(payload_len: u32) -> Self {
Self {
chunk_type: ChunkType::Metadata,
payload_len,
item_count: 0,
}
}
#[inline]
pub fn is_end(&self) -> bool {
matches!(self.chunk_type, ChunkType::End)
}
#[inline]
pub fn is_data(&self) -> bool {
matches!(self.chunk_type, ChunkType::Data)
}
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut bytes = [0u8; Self::SIZE];
bytes[0..4].copy_from_slice(&CHUNK_MAGIC);
bytes[4] = self.chunk_type as u8;
bytes[5..9].copy_from_slice(&self.payload_len.to_le_bytes());
bytes[9..13].copy_from_slice(&self.item_count.to_le_bytes());
bytes
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < Self::SIZE {
return Err(Error::UnexpectedEnd {
additional: Self::SIZE - data.len(),
});
}
if data[0..4] != CHUNK_MAGIC {
return Err(Error::InvalidData {
message: "invalid chunk magic",
});
}
let chunk_type = ChunkType::from_byte(data[4]).ok_or(Error::InvalidData {
message: "invalid chunk type",
})?;
let payload_len = u32::from_le_bytes([data[5], data[6], data[7], data[8]]);
let item_count = u32::from_le_bytes([data[9], data[10], data[11], data[12]]);
Ok(Self {
chunk_type,
payload_len,
item_count,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chunk_header_roundtrip() {
let header = ChunkHeader::data(1024, 10);
let bytes = header.to_bytes();
let parsed = ChunkHeader::from_bytes(&bytes).expect("parse failed");
assert_eq!(header, parsed);
}
#[test]
fn test_end_chunk() {
let header = ChunkHeader::end();
assert!(header.is_end());
assert!(!header.is_data());
let bytes = header.to_bytes();
let parsed = ChunkHeader::from_bytes(&bytes).expect("parse failed");
assert!(parsed.is_end());
}
#[test]
fn test_data_chunk() {
let header = ChunkHeader::data(500, 5);
assert!(header.is_data());
assert!(!header.is_end());
assert_eq!(header.payload_len, 500);
assert_eq!(header.item_count, 5);
}
#[test]
fn test_metadata_chunk() {
let header = ChunkHeader::metadata(256);
assert!(!header.is_data());
assert!(!header.is_end());
assert_eq!(header.payload_len, 256);
}
#[test]
fn test_invalid_magic() {
let mut bytes = ChunkHeader::data(100, 1).to_bytes();
bytes[0] = 0xFF;
let result = ChunkHeader::from_bytes(&bytes);
assert!(result.is_err());
}
#[test]
fn test_header_size() {
assert_eq!(ChunkHeader::SIZE, 13);
}
}