pub const USERBLOCK_SIZE: u64 = 512;
pub const DEFAULT_DESCRIPTION: &str =
"MATLAB 7.3 MAT-file, Platform: hdf5-pure (Rust)";
pub fn write_header(file_bytes: &mut [u8], description: &str) {
assert!(
file_bytes.len() >= USERBLOCK_SIZE as usize,
"userblock requires at least 512 bytes, got {}",
file_bytes.len()
);
for b in file_bytes[..124].iter_mut() {
*b = b' ';
}
let bytes = description.as_bytes();
let n = bytes.len().min(124);
for (dst, src) in file_bytes[..n].iter_mut().zip(bytes[..n].iter()) {
*dst = if src.is_ascii() && *src != 0 { *src } else { b'?' };
}
file_bytes[124] = 0x00;
file_bytes[125] = 0x02;
file_bytes[126] = b'I';
file_bytes[127] = b'M';
}
pub fn verify_header(file_bytes: &[u8]) -> Result<(), &'static str> {
if file_bytes.len() < USERBLOCK_SIZE as usize {
return Err("file too short for MAT v7.3 userblock");
}
if !file_bytes[..6].eq_ignore_ascii_case(b"MATLAB") {
return Err("missing `MATLAB` signature at byte 0");
}
if file_bytes[126] != b'I' || file_bytes[127] != b'M' {
return Err("missing `IM` little-endian indicator at byte 126");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_header_passes_verify() {
let mut buf = [0u8; 1024];
write_header(&mut buf, DEFAULT_DESCRIPTION);
verify_header(&buf).unwrap();
assert_eq!(&buf[..6], b"MATLAB");
assert_eq!(&buf[124..128], &[0x00, 0x02, b'I', b'M']);
assert_eq!(buf[200], 0, "bytes past 128 must stay zero");
}
#[test]
fn truncated_description_fills_first_124_bytes() {
let mut buf = [0u8; 512];
let long = format!("MATLAB 7.3 MAT-file, {}", "x".repeat(500));
write_header(&mut buf, &long);
verify_header(&buf).unwrap();
assert_eq!(&buf[..6], b"MATLAB");
assert_eq!(buf[123], b'x');
assert_eq!(buf[124], 0x00);
assert_eq!(buf[125], 0x02);
}
#[test]
fn verify_rejects_wrong_magic() {
let buf = [0u8; 512];
assert!(verify_header(&buf).is_err());
}
}