use crate::error::{Mf4Error, Result};
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
pub const BLOCK_HEADER_SIZE: usize = 24;
pub const ID_BLOCK_SIZE: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockHeader {
pub block_type: [u8; 4],
pub reserved: [u8; 4],
pub length: u64,
pub link_count: u64,
pub offset: u64,
}
impl BlockHeader {
pub fn parse(data: &[u8], offset: u64) -> Result<Self> {
if data.len() < BLOCK_HEADER_SIZE {
return Err(Mf4Error::truncated(offset, BLOCK_HEADER_SIZE, data.len()));
}
let mut cursor = Cursor::new(data);
let mut block_type = [0u8; 4];
std::io::Read::read_exact(&mut cursor, &mut block_type)?;
let mut reserved = [0u8; 4];
std::io::Read::read_exact(&mut cursor, &mut reserved)?;
let length = cursor.read_u64::<LittleEndian>()?;
let link_count = cursor.read_u64::<LittleEndian>()?;
if length < BLOCK_HEADER_SIZE as u64 {
return Err(Mf4Error::invalid_block_size(
String::from_utf8_lossy(&block_type).to_string(),
length,
BLOCK_HEADER_SIZE as u64,
));
}
let links_size = link_count.checked_mul(8).ok_or_else(|| {
Mf4Error::invalid_block_size(
String::from_utf8_lossy(&block_type).to_string(),
length,
u64::MAX,
)
})?;
let minimum = links_size
.checked_add(BLOCK_HEADER_SIZE as u64)
.ok_or_else(|| Mf4Error::parse_error("block header and link table size overflow"))?;
if minimum > length {
return Err(Mf4Error::invalid_block_size(
String::from_utf8_lossy(&block_type).to_string(),
length,
minimum,
));
}
Ok(BlockHeader {
block_type,
reserved,
length,
link_count,
offset,
})
}
pub fn block_type_str(&self) -> &str {
std::str::from_utf8(&self.block_type).unwrap_or("????")
}
pub fn validate_type(&self, expected: &[u8; 4], offset: u64) -> Result<()> {
if &self.block_type != expected {
return Err(Mf4Error::invalid_block_id(
offset,
String::from_utf8_lossy(expected).to_string(),
String::from_utf8_lossy(&self.block_type).to_string(),
));
}
Ok(())
}
pub fn data_size(&self) -> u64 {
self.length
.saturating_sub(BLOCK_HEADER_SIZE as u64)
.saturating_sub(self.link_count.saturating_mul(8))
}
pub fn links_offset(&self) -> usize {
BLOCK_HEADER_SIZE
}
pub fn data_offset(&self) -> usize {
BLOCK_HEADER_SIZE.saturating_add(
usize::try_from(self.link_count)
.unwrap_or(usize::MAX)
.saturating_mul(8),
)
}
}
pub mod block_ids {
pub const ID: &[u8; 4] = b"MDF ";
pub const HD: &[u8; 4] = b"##HD";
pub const FH: &[u8; 4] = b"##FH";
pub const DG: &[u8; 4] = b"##DG";
pub const CG: &[u8; 4] = b"##CG";
pub const CN: &[u8; 4] = b"##CN";
pub const SI: &[u8; 4] = b"##SI";
pub const CC: &[u8; 4] = b"##CC";
pub const TX: &[u8; 4] = b"##TX";
pub const MD: &[u8; 4] = b"##MD";
pub const DT: &[u8; 4] = b"##DT";
pub const SD: &[u8; 4] = b"##SD";
pub const RD: &[u8; 4] = b"##RD";
pub const DZ: &[u8; 4] = b"##DZ";
pub const DL: &[u8; 4] = b"##DL";
pub const LD: &[u8; 4] = b"##LD";
pub const DV: &[u8; 4] = b"##DV";
pub const DI: &[u8; 4] = b"##DI";
pub const HL: &[u8; 4] = b"##HL";
pub const SR: &[u8; 4] = b"##SR";
pub const AT: &[u8; 4] = b"##AT";
pub const EV: &[u8; 4] = b"##EV";
pub const CA: &[u8; 4] = b"##CA";
pub const CH: &[u8; 4] = b"##CH";
}
pub fn read_link(data: &[u8], offset: usize) -> Result<u64> {
if offset > data.len() || data.len() - offset < 8 {
return Err(Mf4Error::truncated(
offset as u64,
8,
data.len().saturating_sub(offset),
));
}
let mut cursor = Cursor::new(&data[offset..offset + 8]);
Ok(cursor.read_u64::<LittleEndian>()?)
}
pub fn read_links(data: &[u8], offset: usize, count: usize) -> Result<Vec<u64>> {
if offset > data.len() || count > (data.len() - offset) / 8 {
return Err(Mf4Error::truncated(
offset as u64,
count.saturating_mul(8),
data.len().saturating_sub(offset),
));
}
let mut links = Vec::with_capacity(count);
for i in 0..count {
links.push(read_link(data, offset + i * 8)?);
}
Ok(links)
}
pub trait ParseBlock: Sized {
fn parse(data: &[u8], offset: u64) -> Result<Self>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_header_parse() {
let mut data = vec![0u8; 24];
data[0..4].copy_from_slice(b"##HD");
data[4..8].copy_from_slice(&[0, 0, 0, 0]); data[8..16].copy_from_slice(&104u64.to_le_bytes()); data[16..24].copy_from_slice(&6u64.to_le_bytes());
let header = BlockHeader::parse(&data, 0).unwrap();
assert_eq!(header.block_type, *b"##HD");
assert_eq!(header.length, 104);
assert_eq!(header.link_count, 6);
}
#[test]
fn test_block_header_too_short() {
let data = vec![0u8; 10];
let result = BlockHeader::parse(&data, 0);
assert!(result.is_err());
}
#[test]
fn test_read_link() {
let mut data = vec![0u8; 16];
data[0..8].copy_from_slice(&0x1234567890ABCDEFu64.to_le_bytes());
data[8..16].copy_from_slice(&0xFEDCBA0987654321u64.to_le_bytes());
assert_eq!(read_link(&data, 0).unwrap(), 0x1234567890ABCDEF);
assert_eq!(read_link(&data, 8).unwrap(), 0xFEDCBA0987654321);
}
#[test]
fn test_read_links() {
let mut data = vec![0u8; 24];
data[0..8].copy_from_slice(&100u64.to_le_bytes());
data[8..16].copy_from_slice(&200u64.to_le_bytes());
data[16..24].copy_from_slice(&300u64.to_le_bytes());
let links = read_links(&data, 0, 3).unwrap();
assert_eq!(links, vec![100, 200, 300]);
}
}