use crate::{
error::Error,
util::{read_i32_le, read_u32_le},
};
pub const BLOCKS_NUM: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BlockType {
Pages = 0,
Sections = 1,
Entries = 2,
Strings = 3,
LangTables = 4,
CtlColors = 5,
BgFont = 6,
Data = 7,
}
impl BlockType {
pub fn name(self) -> &'static str {
match self {
BlockType::Pages => "Pages",
BlockType::Sections => "Sections",
BlockType::Entries => "Entries",
BlockType::Strings => "Strings",
BlockType::LangTables => "LangTables",
BlockType::CtlColors => "CtlColors",
BlockType::BgFont => "BgFont",
BlockType::Data => "Data",
}
}
pub fn from_index(index: usize) -> Result<Self, Error> {
match index {
0 => Ok(BlockType::Pages),
1 => Ok(BlockType::Sections),
2 => Ok(BlockType::Entries),
3 => Ok(BlockType::Strings),
4 => Ok(BlockType::LangTables),
5 => Ok(BlockType::CtlColors),
6 => Ok(BlockType::BgFont),
7 => Ok(BlockType::Data),
_ => Err(Error::InvalidBlockIndex { index }),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlockHeader<'a> {
bytes: &'a [u8],
}
pub const EMPTY_BLOCK: BlockHeader<'static> = BlockHeader { bytes: &[0u8; 8] };
impl<'a> BlockHeader<'a> {
pub const SIZE: usize = 8;
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
if data.len() < Self::SIZE {
return Err(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "BlockHeader",
});
}
Ok(Self {
bytes: data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "BlockHeader",
})?,
})
}
#[inline]
pub fn offset(&self) -> u32 {
read_u32_le(self.bytes, 0)
}
#[inline]
pub fn num(&self) -> i32 {
read_i32_le(self.bytes, 4)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid() {
let mut buf = [0u8; 8];
buf[0..4].copy_from_slice(&256u32.to_le_bytes());
buf[4..8].copy_from_slice(&10i32.to_le_bytes());
let bh = BlockHeader::parse(&buf).unwrap();
assert_eq!(bh.offset(), 256);
assert_eq!(bh.num(), 10);
}
#[test]
fn parse_too_short() {
let buf = [0u8; 7];
assert_eq!(
BlockHeader::parse(&buf),
Err(Error::TooShort {
expected: 8,
actual: 7,
context: "BlockHeader",
})
);
}
#[test]
fn block_type_from_index() {
assert_eq!(BlockType::from_index(0).unwrap(), BlockType::Pages);
assert_eq!(BlockType::from_index(7).unwrap(), BlockType::Data);
assert_eq!(
BlockType::from_index(8),
Err(Error::InvalidBlockIndex { index: 8 })
);
}
#[test]
fn block_type_name() {
assert_eq!(BlockType::Pages.name(), "Pages");
assert_eq!(BlockType::Strings.name(), "Strings");
assert_eq!(BlockType::Data.name(), "Data");
}
#[test]
fn block_header_is_copy() {
let mut buf = [0u8; 8];
buf[0..4].copy_from_slice(&100u32.to_le_bytes());
buf[4..8].copy_from_slice(&5i32.to_le_bytes());
let bh1 = BlockHeader::parse(&buf).unwrap();
let bh2 = bh1; assert_eq!(bh1, bh2);
}
#[test]
fn roundtrip_all_block_types() {
for i in 0..BLOCKS_NUM {
let bt = BlockType::from_index(i).unwrap();
assert_eq!(bt as u8 as usize, i);
}
}
}