#[cfg(test)]
use crate::error::ParseError;
pub const MAGIC: [u8; 4] = *b"MIRX";
pub const VERSION_MAJOR: u8 = 1;
pub const VERSION_MINOR: u8 = 0;
pub const FILE_HEADER_LEN: usize = 8;
pub const FLAT_HEADER_LEN: usize = 28;
pub const CHUNK_FILE_HEADER_LEN: usize = 44;
pub const CHUNK_TABLE_ENTRY_LEN: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Layout {
Flat = 0x00,
Chunk = 0x01,
}
impl Layout {
pub const fn from_u8(b: u8) -> Option<Self> {
Some(match b {
0x00 => Self::Flat,
0x01 => Self::Chunk,
_ => return None,
})
}
pub const fn to_u8(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileHeader {
pub version_major: u8,
pub version_minor: u8,
pub layout: Layout,
pub flags: u8,
}
impl FileHeader {
#[cfg(test)]
pub fn parse(buf: &[u8]) -> Result<Self, ParseError> {
if buf.len() < FILE_HEADER_LEN {
return Err(ParseError::Truncated);
}
if buf[0..4] != MAGIC {
return Err(ParseError::BadMagic);
}
let version_major = buf[4];
let version_minor = buf[5];
if version_major != VERSION_MAJOR || version_minor != VERSION_MINOR {
return Err(ParseError::UnsupportedVersion {
major: version_major,
minor: version_minor,
});
}
let layout = Layout::from_u8(buf[6]).ok_or(ParseError::UnknownLayout(buf[6]))?;
let flags = buf[7];
if flags != 0 {
return Err(ParseError::ReservedNonZero);
}
Ok(Self {
version_major,
version_minor,
layout,
flags,
})
}
pub fn write_into(&self, out: &mut [u8; FILE_HEADER_LEN]) {
out[0..4].copy_from_slice(&MAGIC);
out[4] = self.version_major;
out[5] = self.version_minor;
out[6] = self.layout.to_u8();
out[7] = self.flags;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FlatHeader {
pub file: FileHeader,
pub color_format: u8,
pub width: u32,
pub height: u32,
pub stride: u32,
pub header_crc32: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkFileHeader {
pub file: FileHeader,
pub chunk_count: u16,
pub chunk_table_offset: u32,
pub file_size: u32,
pub primary_chunk_type: u16,
pub primary_sample_layout: u16,
pub primary_width: u32,
pub primary_height: u32,
pub primary_stride: u32,
pub header_crc32: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkEntry {
pub chunk_type: u16,
pub chunk_flags: u16,
pub chunk_offset: u32,
pub chunk_size: u32,
}
impl ChunkEntry {
pub const FLAG_CRITICAL: u16 = 1 << 0;
#[cfg(test)]
pub(crate) const fn is_critical(&self) -> bool {
self.chunk_flags & Self::FLAG_CRITICAL != 0
}
}
pub mod chunk_type {
pub const IMAGE: u16 = 0x0001;
pub const FRAMES: u16 = 0x0002;
pub const VECTOR: u16 = 0x0003;
pub const FONT: u16 = 0x0004;
pub const META: u16 = 0x0010;
pub const PALETTE: u16 = 0x0080;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minimal_flat_prefix() {
let mut buf = [0u8; FILE_HEADER_LEN];
buf[..4].copy_from_slice(&MAGIC);
buf[4] = 1;
buf[5] = 0;
buf[6] = 0x00;
buf[7] = 0;
let header = FileHeader::parse(&buf).unwrap();
assert_eq!(header.version_major, 1);
assert_eq!(header.version_minor, 0);
assert_eq!(header.layout, Layout::Flat);
assert_eq!(header.flags, 0);
}
#[test]
fn truncated_buffer_is_rejected() {
assert_eq!(FileHeader::parse(&[]), Err(ParseError::Truncated));
assert_eq!(FileHeader::parse(b"MI"), Err(ParseError::Truncated));
}
#[test]
fn bad_magic_is_rejected() {
let bad = [0u8; FILE_HEADER_LEN];
assert_eq!(FileHeader::parse(&bad), Err(ParseError::BadMagic));
}
#[test]
fn unsupported_major_version_is_rejected() {
let mut buf = [0u8; FILE_HEADER_LEN];
buf[..4].copy_from_slice(&MAGIC);
buf[4] = 99;
buf[5] = 0;
buf[6] = 0x00;
assert_eq!(
FileHeader::parse(&buf),
Err(ParseError::UnsupportedVersion {
major: 99,
minor: 0
})
);
}
#[test]
fn unknown_layout_is_rejected() {
let mut buf = [0u8; FILE_HEADER_LEN];
buf[..4].copy_from_slice(&MAGIC);
buf[4] = 1;
buf[5] = 0;
buf[6] = 0x42;
assert_eq!(
FileHeader::parse(&buf),
Err(ParseError::UnknownLayout(0x42))
);
}
#[test]
fn write_then_parse_round_trips() {
let h = FileHeader {
version_major: 1,
version_minor: 0,
layout: Layout::Chunk,
flags: 0,
};
let mut buf = [0u8; FILE_HEADER_LEN];
h.write_into(&mut buf);
assert_eq!(FileHeader::parse(&buf), Ok(h));
}
#[test]
fn chunk_entry_critical_bit() {
let plain = ChunkEntry {
chunk_type: chunk_type::IMAGE,
chunk_flags: 0,
chunk_offset: 0,
chunk_size: 0,
};
assert!(!plain.is_critical());
let critical = ChunkEntry {
chunk_flags: ChunkEntry::FLAG_CRITICAL,
..plain
};
assert!(critical.is_critical());
}
}