use crate::error::{FasError, Result};
use crate::fas::bytes;
pub const SIGNATURE: u32 = 0x1A73_6166;
#[derive(Debug, Clone)]
pub struct Header {
pub major: u8,
pub minor: u8,
pub header_len: u16,
pub input_name_off: u32,
pub output_name_off: u32,
pub strings_off: u32,
pub strings_len: u32,
pub symbols_off: u32,
pub symbols_len: u32,
pub preprocessed_off: u32,
pub preprocessed_len: u32,
pub dump_off: Option<u32>,
pub dump_len: Option<u32>,
pub sections: Option<TableRange>,
pub references: Option<TableRange>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TableRange {
pub offset: u32,
pub length: u32,
}
impl Header {
pub fn parse(data: &[u8]) -> Result<Self> {
if bytes::u32_at(data, 0)? != SIGNATURE {
return Err(FasError::BadSignature);
}
let header_len = bytes::u16_at(data, 6)?;
if header_len < 40 || data.len() < header_len as usize {
return Err(FasError::Truncated("header"));
}
let mut h = Self {
major: bytes::u8_at(data, 4)?,
minor: bytes::u8_at(data, 5)?,
header_len,
input_name_off: bytes::u32_at(data, 8)?,
output_name_off: bytes::u32_at(data, 12)?,
strings_off: bytes::u32_at(data, 16)?,
strings_len: bytes::u32_at(data, 20)?,
symbols_off: bytes::u32_at(data, 24)?,
symbols_len: bytes::u32_at(data, 28)?,
preprocessed_off: bytes::u32_at(data, 32)?,
preprocessed_len: bytes::u32_at(data, 36)?,
dump_off: None,
dump_len: None,
sections: None,
references: None,
};
if header_len as usize >= 48 {
let dump_len = bytes::u32_at(data, 44)?;
if dump_len != 0 {
h.dump_off = Some(bytes::u32_at(data, 40)?);
h.dump_len = Some(dump_len);
}
}
if header_len as usize >= 56 {
h.sections = Some(TableRange {
offset: bytes::u32_at(data, 48)?,
length: bytes::u32_at(data, 52)?,
});
}
if header_len as usize >= 64 {
h.references = Some(TableRange {
offset: bytes::u32_at(data, 56)?,
length: bytes::u32_at(data, 60)?,
});
}
h.validate(data)?;
Ok(h)
}
pub fn strings<'a>(&self, data: &'a [u8]) -> Result<&'a [u8]> {
bytes::slice_at(data, self.strings_off, self.strings_len)
}
pub fn symbols<'a>(&self, data: &'a [u8]) -> Result<&'a [u8]> {
bytes::slice_at(data, self.symbols_off, self.symbols_len)
}
pub fn preprocessed<'a>(&self, data: &'a [u8]) -> Result<&'a [u8]> {
bytes::slice_at(data, self.preprocessed_off, self.preprocessed_len)
}
pub fn dump<'a>(&self, data: &'a [u8]) -> Result<Option<&'a [u8]>> {
match (self.dump_off, self.dump_len) {
(Some(off), Some(len)) => Ok(Some(bytes::slice_at(data, off, len)?)),
_ => Ok(None),
}
}
pub fn section_names<'a>(&self, data: &'a [u8]) -> Result<Option<&'a [u8]>> {
self.optional_table(data, self.sections)
}
pub fn symbol_references<'a>(&self, data: &'a [u8]) -> Result<Option<&'a [u8]>> {
self.optional_table(data, self.references)
}
pub fn input_name<'a>(&self, data: &'a [u8]) -> Result<&'a str> {
self.string_at(data, self.input_name_off)
}
pub fn output_name<'a>(&self, data: &'a [u8]) -> Result<&'a str> {
self.string_at(data, self.output_name_off)
}
pub fn string_at<'a>(&self, data: &'a [u8], off: u32) -> Result<&'a str> {
let table = self.strings(data)?;
bytes::cstring_at(table, off as usize)
}
fn optional_table<'a>(
&self,
data: &'a [u8],
table: Option<TableRange>,
) -> Result<Option<&'a [u8]>> {
table
.map(|range| bytes::slice_at(data, range.offset, range.length))
.transpose()
}
fn validate(&self, data: &[u8]) -> Result<()> {
self.strings(data)?;
self.symbols(data)?;
self.preprocessed(data)?;
self.dump(data)?;
self.section_names(data)?;
self.symbol_references(data)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn header(length: u16) -> Vec<u8> {
let mut data = vec![0u8; 96];
data[0..4].copy_from_slice(&SIGNATURE.to_le_bytes());
data[4] = 1;
data[5] = 73;
data[6..8].copy_from_slice(&length.to_le_bytes());
data[16..20].copy_from_slice(&64u32.to_le_bytes());
data[20..24].copy_from_slice(&2u32.to_le_bytes());
data[24..28].copy_from_slice(&66u32.to_le_bytes());
data[32..36].copy_from_slice(&66u32.to_le_bytes());
data[64] = 0;
data[65] = 0;
data
}
#[test]
fn distinguishes_missing_and_empty_optional_tables() {
let old = Header::parse(&header(48)).unwrap();
assert_eq!(old.sections, None);
assert_eq!(old.references, None);
let current = Header::parse(&header(64)).unwrap();
assert_eq!(
current.sections,
Some(TableRange {
offset: 0,
length: 0
})
);
assert_eq!(
current.references,
Some(TableRange {
offset: 0,
length: 0
})
);
}
#[test]
fn rejects_optional_table_outside_file() {
let mut data = header(64);
data[48..52].copy_from_slice(&95u32.to_le_bytes());
data[52..56].copy_from_slice(&4u32.to_le_bytes());
assert!(matches!(
Header::parse(&data),
Err(FasError::Truncated("slice"))
));
}
}