#[derive(Debug, Clone, PartialEq)]
pub struct AsmHeader {
pub width: u8,
pub version_word: Option<u64>,
pub format_version: Option<u64>,
pub schema_version: Option<u64>,
pub product_family: Option<String>,
pub product_version: Option<String>,
pub save_date: Option<String>,
pub scale: Option<f64>,
pub linear: Option<f64>,
pub angular: Option<f64>,
}
const MAGIC_PREFIX: &[u8] = b"ASM BinaryFile";
const STRING_REGION_START: usize = 47;
pub fn has_asm_magic(bytes: &[u8]) -> bool {
bytes.len() >= 16
&& bytes.starts_with(MAGIC_PREFIX)
&& bytes[14].is_ascii_digit()
&& bytes[15] == b'<'
}
pub fn parse(bytes: &[u8]) -> Option<AsmHeader> {
if !has_asm_magic(bytes) {
return None;
}
let width = bytes[14] - b'0';
let mut header = AsmHeader {
width,
version_word: None,
format_version: None,
schema_version: None,
product_family: None,
product_version: None,
save_date: None,
scale: None,
linear: None,
angular: None,
};
if width != 8 || bytes.len() < 48 {
return Some(header);
}
header.version_word = read_be_u64(bytes, 24);
header.format_version = read_be_u64(bytes, 32);
header.schema_version = read_be_u64(bytes, 40);
let mut cur = STRING_REGION_START;
let mut strings = Vec::new();
while strings.len() < 3 {
match read_u8_string(bytes, cur) {
Some((s, next)) => {
strings.push(s);
cur = next;
}
None => break,
}
}
let mut doubles = Vec::new();
while doubles.len() < 3 {
match read_tagged_f64(bytes, cur) {
Some((v, next)) => {
doubles.push(v);
cur = next;
}
None => break,
}
}
let mut it = strings.into_iter();
header.product_family = it.next();
header.product_version = it.next();
header.save_date = it.next();
let mut dit = doubles.into_iter();
header.scale = dit.next();
header.linear = dit.next();
header.angular = dit.next();
Some(header)
}
pub fn record_stream_start(bytes: &[u8]) -> Option<usize> {
if !has_asm_magic(bytes) {
return None;
}
let width = bytes[14] - b'0';
if width != 8 || bytes.len() < 48 {
return None;
}
let mut cur = STRING_REGION_START;
let mut strings = 0;
while strings < 3 {
match read_u8_string(bytes, cur) {
Some((_, next)) => {
strings += 1;
cur = next;
}
None => return None,
}
}
let mut doubles = 0;
while doubles < 3 {
match read_tagged_f64(bytes, cur) {
Some((_, next)) => {
doubles += 1;
cur = next;
}
None => return None,
}
}
Some(cur)
}
const DELTA_STATE: &[u8] = b"delta_state";
pub fn first_delta_state_offset(bytes: &[u8]) -> Option<usize> {
bytes
.windows(DELTA_STATE.len())
.position(|w| w == DELTA_STATE)
}
fn read_be_u64(bytes: &[u8], at: usize) -> Option<u64> {
bytes.get(at..at + 8).map(|s| {
u64::from_be_bytes(
s.try_into()
.expect("invariant: bytes.get(at..at+8) is an 8-byte slice"),
)
})
}
fn read_u8_string(bytes: &[u8], at: usize) -> Option<(String, usize)> {
if *bytes.get(at)? != 0x07 {
return None;
}
let len = *bytes.get(at + 1)? as usize;
let start = at + 2;
let slice = bytes.get(start..start + len)?;
let s = std::str::from_utf8(slice).ok()?.to_string();
Some((s, start + len))
}
fn read_tagged_f64(bytes: &[u8], at: usize) -> Option<(f64, usize)> {
if *bytes.get(at)? != 0x06 {
return None;
}
let slice = bytes.get(at + 1..at + 9)?;
Some((
f64::from_le_bytes(
slice
.try_into()
.expect("invariant: bytes.get(at+1..at+9) is an 8-byte slice"),
),
at + 9,
))
}