use crate::kernel_header::{
read_string_region, scan_string_region, BinaryHeader, HeaderRegion, KernelHeader, RefWidth,
};
use crate::layout::asmheader_binaryfile4 as bf4;
use crate::layout::asmheader_binaryfile8 as bf8;
use cadmpeg_core::decode::{DecodeContext, View};
use cadmpeg_core::CodecError;
const MAGIC_PREFIX: &[u8] = b"ASM BinaryFile";
pub fn has_asm_magic(bytes: &[u8]) -> bool {
declared_width(bytes).is_some()
}
fn declared_width(bytes: &[u8]) -> Option<RefWidth> {
if bytes.len() < 16 || !bytes.starts_with(MAGIC_PREFIX) {
return None;
}
match bytes[14] {
b'4' => Some(RefWidth::Four),
b'8' => Some(RefWidth::Eight),
_ => None,
}
}
pub fn record_count(bytes: &[u8]) -> Option<u32> {
if declared_width(bytes) != Some(RefWidth::Four) {
return None;
}
View::u32_le_at(bytes, bf4::RECORD_COUNT)
}
pub fn parse(ctx: &DecodeContext<'_>, bytes: &[u8]) -> Result<Option<BinaryHeader>, CodecError> {
let Some(width) = declared_width(bytes) else {
return Ok(None);
};
let mut header = KernelHeader {
save_format_version: None,
entity_count: None,
flags: None,
product_family: None,
product_version: None,
save_date: None,
scale: None,
linear: None,
angular: None,
};
match width {
RefWidth::Eight => {
header.save_format_version = View::u32_le_at(bytes, bf8::SAVE_FORMAT_VERSION);
header.entity_count = View::u64_le_at(bytes, bf8::ENTITY_COUNT);
header.flags = View::u64_le_at(bytes, bf8::FLAGS);
}
RefWidth::Four => {
header.save_format_version = View::u32_le_at(bytes, bf4::SAVE_FORMAT_VERSION);
header.entity_count = View::u32_le_at(bytes, bf4::ENTITY_COUNT).map(u64::from);
header.flags = View::u32_le_at(bytes, bf4::FLAGS).map(u64::from);
}
}
let start = match width {
RefWidth::Four => bf4::LEN,
RefWidth::Eight => bf8::LEN,
};
let HeaderRegion {
strings: [family, version, date],
doubles: [scale, linear, angular],
} = read_string_region(ctx, bytes, start)?;
header.product_family = family;
header.product_version = version;
header.save_date = date;
header.scale = scale;
header.linear = linear;
header.angular = angular;
Ok(Some(BinaryHeader {
width,
metadata: header,
}))
}
pub fn record_stream_start(bytes: &[u8]) -> Option<usize> {
let width = declared_width(bytes)?;
record_stream_start_with_width(bytes, width)
}
pub fn record_stream_start_with_header(bytes: &[u8], header: &BinaryHeader) -> Option<usize> {
record_stream_start_with_width(bytes, header.width)
}
fn record_stream_start_with_width(bytes: &[u8], width: RefWidth) -> Option<usize> {
let start = match width {
RefWidth::Eight => bf8::LEN,
RefWidth::Four => bf4::LEN,
};
let (strings, doubles, cur) = scan_string_region(bytes, start);
(strings == 3 && doubles == 3).then_some(cur)
}
pub fn solved_record_limit(
ctx: &DecodeContext<'_>,
bytes: &[u8],
) -> Result<Option<usize>, CodecError> {
let Some((start, width)) = (|| {
let width = declared_width(bytes)?;
let flags = match width {
RefWidth::Four => View::u32_le_at(bytes, bf4::FLAGS).map(u64::from),
RefWidth::Eight => View::u64_le_at(bytes, bf8::FLAGS),
}?;
if flags & crate::kernel_header::HISTORY_PARTITION_FLAG == 0 {
return None;
}
let start = record_stream_start_with_width(bytes, width)?;
Some((start, width))
})() else {
return Ok(None);
};
crate::sab::scan_history_boundary(
ctx,
bytes,
start,
width,
Some(&["Begin", "of", "ASM", "History", "Data"]),
)
}
pub fn solved_record_limit_with_header(
ctx: &DecodeContext<'_>,
bytes: &[u8],
header: &BinaryHeader,
) -> Result<Option<usize>, CodecError> {
if !header.metadata.has_history_partition() {
return Ok(None);
}
let Some(start) = record_stream_start_with_header(bytes, header) else {
return Ok(None);
};
crate::sab::scan_history_boundary(
ctx,
bytes,
start,
header.width,
Some(&["Begin", "of", "ASM", "History", "Data"]),
)
}
#[cfg(test)]
mod tests {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy, ResourceDimension};
use cadmpeg_core::CodecError;
#[test]
fn asm_product_string_refuses_retained_limit_before_copy() {
let mut bytes = b"ASM BinaryFile4".to_vec();
bytes.resize(crate::layout::asmheader_binaryfile4::LEN, 0);
bytes.extend_from_slice(&[7, 3]);
bytes.extend_from_slice(b"ASM");
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_retained_bytes = 2;
let (limited, _) = DecodeContext::from_root_bytes(&bytes, &arena, &policy).unwrap();
assert!(matches!(
super::parse(&limited, &bytes),
Err(CodecError::ResourceLimit(limit))
if limit.dimension == ResourceDimension::RetainedBytes
&& limit.operation == "retain kernel header product string"
));
let (service, _) =
DecodeContext::from_root_bytes(&bytes, &arena, &DecodePolicy::service()).unwrap();
assert_eq!(
super::parse(&service, &bytes)
.unwrap()
.unwrap()
.metadata
.product_family
.as_deref(),
Some("ASM")
);
}
}