use crate::{error::Error, reader::Reader};
pub const IFPS_MAGIC: [u8; 4] = *b"IFPS";
pub const PS_VALID_HEADER: u32 = 0x5350_4649;
pub const PS_LOW_BUILD_SUPPORT: u32 = 12;
pub const PS_CURRENT_BUILD_NO: u32 = 23;
pub const INVALID_VAL: u32 = u32::MAX;
pub const HEADER_SIZE: usize = 28;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Header {
pub build_no: u32,
pub type_count: u32,
pub proc_count: u32,
pub var_count: u32,
pub main_proc_no: u32,
pub import_table_size: u32,
}
impl Header {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
let mut reader = Reader::new(bytes);
let magic = reader.array::<4>("IFPS magic")?;
if magic != IFPS_MAGIC {
return Err(Error::BadMagic { got: magic });
}
let build_no = reader.u32_le("PSBuildNo")?;
if !(PS_LOW_BUILD_SUPPORT..=PS_CURRENT_BUILD_NO).contains(&build_no) {
return Err(Error::UnsupportedBuild { build_no });
}
let type_count = reader.u32_le("TypeCount")?;
let proc_count = reader.u32_le("ProcCount")?;
let var_count = reader.u32_le("VarCount")?;
let main_proc_no = reader.u32_le("MainProcNo")?;
let import_table_size = reader.u32_le("ImportTableSize")?;
Ok(Self {
build_no,
type_count,
proc_count,
var_count,
main_proc_no,
import_table_size,
})
}
pub fn has_no_main_proc(&self) -> bool {
self.main_proc_no == INVALID_VAL
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synth(magic: [u8; 4], build: u32) -> Vec<u8> {
let mut v = Vec::with_capacity(HEADER_SIZE);
v.extend_from_slice(&magic);
v.extend_from_slice(&build.to_le_bytes());
v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v.extend_from_slice(&INVALID_VAL.to_le_bytes()); v.extend_from_slice(&0u32.to_le_bytes()); v
}
#[test]
fn rejects_short_input() {
let err = Header::parse(b"IFPS").unwrap_err();
assert!(matches!(err, Error::Truncated { .. }));
}
#[test]
fn rejects_bad_magic() {
let err = Header::parse(&synth(*b"XXXX", 23)).unwrap_err();
assert!(matches!(err, Error::BadMagic { got } if got == *b"XXXX"));
}
#[test]
fn rejects_too_old_build() {
let err = Header::parse(&synth(IFPS_MAGIC, 11)).unwrap_err();
assert!(matches!(err, Error::UnsupportedBuild { build_no: 11 }));
}
#[test]
fn rejects_too_new_build() {
let err = Header::parse(&synth(IFPS_MAGIC, 99)).unwrap_err();
assert!(matches!(err, Error::UnsupportedBuild { build_no: 99 }));
}
#[test]
fn accepts_valid_header() {
let header = Header::parse(&synth(IFPS_MAGIC, 23)).unwrap();
assert_eq!(header.build_no, 23);
assert_eq!(header.type_count, 0);
assert!(header.has_no_main_proc());
}
}