feat: parse and display XEX2 main header (M1)
Implement XEX2 main header parsing with module flag decoding. Add error handling, big-endian read utilities, CLI entry point, and comprehensive unit + integration tests against a sample file. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
117
src/util.rs
Normal file
117
src/util.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
/// Big-endian binary read helpers with bounds checking.
|
||||
use crate::error::{Result, Xex2Error};
|
||||
|
||||
/// Reads a big-endian `u32` from `data` at the given byte `offset`.
|
||||
pub fn read_u32_be(data: &[u8], offset: usize) -> Result<u32> {
|
||||
let end = offset + 4;
|
||||
if end > data.len() {
|
||||
return Err(Xex2Error::FileTooSmall {
|
||||
expected: end,
|
||||
actual: data.len(),
|
||||
});
|
||||
}
|
||||
Ok(u32::from_be_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
]))
|
||||
}
|
||||
|
||||
/// Reads a big-endian `u16` from `data` at the given byte `offset`.
|
||||
pub fn read_u16_be(data: &[u8], offset: usize) -> Result<u16> {
|
||||
let end = offset + 2;
|
||||
if end > data.len() {
|
||||
return Err(Xex2Error::FileTooSmall {
|
||||
expected: end,
|
||||
actual: data.len(),
|
||||
});
|
||||
}
|
||||
Ok(u16::from_be_bytes([data[offset], data[offset + 1]]))
|
||||
}
|
||||
|
||||
/// Reads a single byte from `data` at the given `offset`.
|
||||
pub fn read_u8(data: &[u8], offset: usize) -> Result<u8> {
|
||||
if offset >= data.len() {
|
||||
return Err(Xex2Error::FileTooSmall {
|
||||
expected: offset + 1,
|
||||
actual: data.len(),
|
||||
});
|
||||
}
|
||||
Ok(data[offset])
|
||||
}
|
||||
|
||||
/// Returns a byte slice of `len` bytes from `data` starting at `offset`.
|
||||
pub fn read_bytes(data: &[u8], offset: usize, len: usize) -> Result<&[u8]> {
|
||||
let end = offset + len;
|
||||
if end > data.len() {
|
||||
return Err(Xex2Error::FileTooSmall {
|
||||
expected: end,
|
||||
actual: data.len(),
|
||||
});
|
||||
}
|
||||
Ok(&data[offset..end])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_read_u32_be() {
|
||||
let data = [0x58, 0x45, 0x58, 0x32];
|
||||
assert_eq!(read_u32_be(&data, 0).unwrap(), 0x58455832);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u32_be_with_offset() {
|
||||
let data = [0x00, 0x00, 0x58, 0x45, 0x58, 0x32];
|
||||
assert_eq!(read_u32_be(&data, 2).unwrap(), 0x58455832);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u16_be() {
|
||||
let data = [0x12, 0x34];
|
||||
assert_eq!(read_u16_be(&data, 0).unwrap(), 0x1234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u8() {
|
||||
let data = [0xAB, 0xCD];
|
||||
assert_eq!(read_u8(&data, 1).unwrap(), 0xCD);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bytes() {
|
||||
let data = [0x01, 0x02, 0x03, 0x04, 0x05];
|
||||
assert_eq!(read_bytes(&data, 1, 3).unwrap(), &[0x02, 0x03, 0x04]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u32_be_out_of_bounds() {
|
||||
let data = [0x00, 0x01];
|
||||
let err = read_u32_be(&data, 0).unwrap_err();
|
||||
assert!(matches!(err, Xex2Error::FileTooSmall { expected: 4, actual: 2 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u16_be_out_of_bounds() {
|
||||
let data = [0x00];
|
||||
let err = read_u16_be(&data, 0).unwrap_err();
|
||||
assert!(matches!(err, Xex2Error::FileTooSmall { expected: 2, actual: 1 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_u8_out_of_bounds() {
|
||||
let data: [u8; 0] = [];
|
||||
let err = read_u8(&data, 0).unwrap_err();
|
||||
assert!(matches!(err, Xex2Error::FileTooSmall { expected: 1, actual: 0 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bytes_out_of_bounds() {
|
||||
let data = [0x01, 0x02];
|
||||
let err = read_bytes(&data, 1, 3).unwrap_err();
|
||||
assert!(matches!(err, Xex2Error::FileTooSmall { expected: 4, actual: 2 }));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user