use crate::{
error::Error,
util::{read_i32_le, read_u32_le},
};
pub const FH_SIG: u32 = 0xDEAD_BEEF;
pub const FH_SIG_ALT: u32 = 0xDEAD_BEED;
pub const FH_INT1: u32 = 0x6C6C_754E;
pub const FH_INT2: u32 = 0x7466_6F73;
pub const FH_INT3: u32 = 0x7473_6E49;
pub const FH_INT1_LEGACY: u32 = 0x7369_736E;
pub const FH_INT2_LEGACY: u32 = 0x7473_6E69;
pub const FH_INT3_LEGACY: u32 = 0x006C_6C61;
pub const FH_FLAGS_MASK: u32 = 0x0F;
pub const FH_FLAGS_UNINSTALL: u32 = 0x01;
pub const FH_FLAGS_SILENT: u32 = 0x02;
pub const FH_FLAGS_NO_CRC: u32 = 0x04;
pub const FH_FLAGS_FORCE_CRC: u32 = 0x08;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FirstHeader<'a> {
bytes: &'a [u8],
}
impl<'a> FirstHeader<'a> {
pub const SIZE: usize = 28;
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: "FirstHeader",
});
}
let bytes = data.get(..Self::SIZE).ok_or(Error::TooShort {
expected: Self::SIZE,
actual: data.len(),
context: "FirstHeader",
})?;
let header = Self { bytes };
let sig = header.siginfo();
if sig != FH_SIG && sig != FH_SIG_ALT {
return Err(Error::InvalidMagic {
expected: FH_SIG,
got: sig,
});
}
let ns = header.nsinst();
let standard = ns[0] == FH_INT1 && ns[1] == FH_INT2 && ns[2] == FH_INT3;
let legacy = ns[0] == FH_INT1_LEGACY && ns[1] == FH_INT2_LEGACY && ns[2] == FH_INT3_LEGACY;
if !standard && !legacy {
return Err(Error::SignatureNotFound);
}
let flags = header.flags();
if flags & !FH_FLAGS_MASK != 0 {
return Err(Error::InvalidFirstHeaderFlags { flags });
}
Ok(header)
}
#[inline]
pub fn flags(&self) -> u32 {
read_u32_le(self.bytes, 0)
}
#[inline]
pub fn siginfo(&self) -> u32 {
read_u32_le(self.bytes, 4)
}
#[inline]
pub fn nsinst(&self) -> [u32; 3] {
[
read_u32_le(self.bytes, 8),
read_u32_le(self.bytes, 12),
read_u32_le(self.bytes, 16),
]
}
#[inline]
pub fn length_of_header(&self) -> i32 {
read_i32_le(self.bytes, 20)
}
#[inline]
pub fn length_of_all_following_data(&self) -> i32 {
read_i32_le(self.bytes, 24)
}
#[inline]
pub fn is_uninstaller(&self) -> bool {
self.flags() & FH_FLAGS_UNINSTALL != 0
}
#[inline]
pub fn is_silent(&self) -> bool {
self.flags() & FH_FLAGS_SILENT != 0
}
#[inline]
pub fn has_no_crc(&self) -> bool {
self.flags() & FH_FLAGS_NO_CRC != 0
}
#[inline]
pub fn has_force_crc(&self) -> bool {
self.flags() & FH_FLAGS_FORCE_CRC != 0
}
#[inline]
pub fn is_legacy(&self) -> bool {
let ns = self.nsinst();
ns[0] == FH_INT1_LEGACY && ns[1] == FH_INT2_LEGACY && ns[2] == FH_INT3_LEGACY
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_standard(flags: u32, header_len: i32, all_len: i32) -> [u8; 28] {
let mut buf = [0u8; 28];
buf[0..4].copy_from_slice(&flags.to_le_bytes());
buf[4..8].copy_from_slice(&FH_SIG.to_le_bytes());
buf[8..12].copy_from_slice(&FH_INT1.to_le_bytes());
buf[12..16].copy_from_slice(&FH_INT2.to_le_bytes());
buf[16..20].copy_from_slice(&FH_INT3.to_le_bytes());
buf[20..24].copy_from_slice(&header_len.to_le_bytes());
buf[24..28].copy_from_slice(&all_len.to_le_bytes());
buf
}
fn make_legacy(flags: u32) -> [u8; 28] {
let mut buf = [0u8; 28];
buf[0..4].copy_from_slice(&flags.to_le_bytes());
buf[4..8].copy_from_slice(&FH_SIG.to_le_bytes());
buf[8..12].copy_from_slice(&FH_INT1_LEGACY.to_le_bytes());
buf[12..16].copy_from_slice(&FH_INT2_LEGACY.to_le_bytes());
buf[16..20].copy_from_slice(&FH_INT3_LEGACY.to_le_bytes());
buf[20..24].copy_from_slice(&512i32.to_le_bytes());
buf[24..28].copy_from_slice(&1024i32.to_le_bytes());
buf
}
#[test]
fn parse_valid_standard() {
let buf = make_standard(0, 4096, 8192);
let fh = FirstHeader::parse(&buf).unwrap();
assert_eq!(fh.flags(), 0);
assert_eq!(fh.siginfo(), FH_SIG);
assert_eq!(fh.nsinst(), [FH_INT1, FH_INT2, FH_INT3]);
assert_eq!(fh.length_of_header(), 4096);
assert_eq!(fh.length_of_all_following_data(), 8192);
assert!(!fh.is_uninstaller());
assert!(!fh.is_silent());
assert!(!fh.has_no_crc());
assert!(!fh.has_force_crc());
assert!(!fh.is_legacy());
}
#[test]
fn parse_uninstaller_flag() {
let buf = make_standard(FH_FLAGS_UNINSTALL, 100, 200);
let fh = FirstHeader::parse(&buf).unwrap();
assert!(fh.is_uninstaller());
assert!(!fh.is_silent());
}
#[test]
fn parse_silent_flag() {
let buf = make_standard(FH_FLAGS_SILENT, 100, 200);
let fh = FirstHeader::parse(&buf).unwrap();
assert!(fh.is_silent());
}
#[test]
fn parse_no_crc_flag() {
let buf = make_standard(FH_FLAGS_NO_CRC, 100, 200);
let fh = FirstHeader::parse(&buf).unwrap();
assert!(fh.has_no_crc());
}
#[test]
fn parse_combined_flags() {
let flags = FH_FLAGS_UNINSTALL | FH_FLAGS_SILENT | FH_FLAGS_FORCE_CRC;
let buf = make_standard(flags, 100, 200);
let fh = FirstHeader::parse(&buf).unwrap();
assert!(fh.is_uninstaller());
assert!(fh.is_silent());
assert!(fh.has_force_crc());
}
#[test]
fn parse_legacy_signature() {
let buf = make_legacy(0);
let fh = FirstHeader::parse(&buf).unwrap();
assert!(fh.is_legacy());
}
#[test]
fn parse_alt_sig() {
let mut buf = make_standard(0, 100, 200);
buf[4..8].copy_from_slice(&FH_SIG_ALT.to_le_bytes());
let fh = FirstHeader::parse(&buf).unwrap();
assert_eq!(fh.siginfo(), FH_SIG_ALT);
}
#[test]
fn parse_too_short() {
let buf = [0u8; 27];
assert_eq!(
FirstHeader::parse(&buf),
Err(Error::TooShort {
expected: 28,
actual: 27,
context: "FirstHeader",
})
);
}
#[test]
fn parse_bad_siginfo() {
let mut buf = make_standard(0, 100, 200);
buf[4..8].copy_from_slice(&0x12345678u32.to_le_bytes());
assert_eq!(
FirstHeader::parse(&buf),
Err(Error::InvalidMagic {
expected: FH_SIG,
got: 0x12345678,
})
);
}
#[test]
fn parse_bad_nsinst() {
let mut buf = make_standard(0, 100, 200);
buf[8..12].copy_from_slice(&0x00000000u32.to_le_bytes());
assert_eq!(FirstHeader::parse(&buf), Err(Error::SignatureNotFound));
}
#[test]
fn parse_invalid_flags() {
let buf = make_standard(0x10, 100, 200); assert_eq!(
FirstHeader::parse(&buf),
Err(Error::InvalidFirstHeaderFlags { flags: 0x10 })
);
}
#[test]
fn parse_with_extra_trailing_data() {
let fh_bytes = make_standard(0, 100, 200);
let mut buf = vec![0u8; 1024];
buf[..28].copy_from_slice(&fh_bytes);
let fh = FirstHeader::parse(&buf).unwrap();
assert_eq!(fh.length_of_header(), 100);
}
#[test]
fn first_header_is_copy() {
let buf = make_standard(0, 100, 200);
let fh1 = FirstHeader::parse(&buf).unwrap();
let fh2 = fh1; assert_eq!(fh1, fh2);
}
}