use crate::kernel_header::RefWidth;
use std::collections::BTreeMap;
use cadmpeg_core::dialect::{Admission, DialectId, DialectMatch, Grammar, LayerInstance};
use crate::kernel_header::{BinaryHeader, KernelHeader};
include!("dialect/registry_ids.rs");
pub const DECLARED_SAVE_FORMAT_MAJOR: &str = "save_format_major";
pub const DECLARED_SAVE_FORMAT_MINOR: &str = "save_format_minor";
pub const DECLARED_REFERENCE_WIDTH: &str = "reference_width";
pub const DECLARED_CARRIER: &str = "carrier";
#[derive(Debug, Clone, Copy)]
pub enum KernelHeaderRef<'a> {
Acis(&'a BinaryHeader),
Asm(&'a BinaryHeader),
TextAsm(&'a KernelHeader),
TextAcis(&'a KernelHeader),
Unknown,
}
#[must_use]
pub fn classify(header: KernelHeaderRef<'_>) -> DialectMatch {
let mut declared = BTreeMap::new();
for (key, value) in declaration_fields(header) {
if let Some(value) = value {
if let Some(key) = cadmpeg_core::text::NonBlankString::new(key) {
declared.insert(key, value.to_string());
}
}
}
match_header(header).with_declared(declared)
}
fn declaration_fields(header: KernelHeaderRef<'_>) -> [(&'static str, Option<u32>); 3] {
let parsed = match header {
KernelHeaderRef::Acis(header) | KernelHeaderRef::Asm(header) => Some(&header.metadata),
KernelHeaderRef::TextAsm(header) | KernelHeaderRef::TextAcis(header) => Some(header),
KernelHeaderRef::Unknown => None,
};
let width = match header {
KernelHeaderRef::Acis(header) | KernelHeaderRef::Asm(header) => Some(match header.width {
RefWidth::Four => 4,
RefWidth::Eight => 8,
}),
_ => None,
};
[
(
DECLARED_SAVE_FORMAT_MAJOR,
parsed.and_then(KernelHeader::save_format_major),
),
(
DECLARED_SAVE_FORMAT_MINOR,
parsed.and_then(KernelHeader::save_format_minor),
),
(DECLARED_REFERENCE_WIDTH, width),
]
}
fn match_header(header: KernelHeaderRef<'_>) -> DialectMatch {
match header {
KernelHeaderRef::Acis(header) => acis_match(header.metadata.save_format_major()),
KernelHeaderRef::Asm(header) => DialectMatch::admitted(asm_binary_row(header.width)),
KernelHeaderRef::TextAsm(_) => DialectMatch::admitted(ACIS_TEXT_ASM),
KernelHeaderRef::TextAcis(header) => {
if acis_band_verified(header.save_format_major()) {
DialectMatch::admitted(ACIS_TEXT_ACIS)
} else {
DialectMatch::residual(ACIS_TEXT_ACIS)
}
}
KernelHeaderRef::Unknown => DialectMatch::refused(ACIS_UNKNOWN),
}
}
pub fn classify_layer(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
header: KernelHeaderRef<'_>,
carrier: &str,
instance: LayerInstance,
) -> Result<DialectMatch, cadmpeg_core::CodecError> {
let operation = "retain kernel dialect declarations";
let mut declared = BTreeMap::new();
for (key, value) in declaration_fields(header) {
if let Some(value) = value {
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(key.len()) + 10,
operation,
)?;
let key =
cadmpeg_core::text::NonBlankString::new(ctx.copy_retained_text(key, operation)?)
.ok_or_else(|| {
cadmpeg_core::CodecError::malformed("empty kernel declaration key")
})?;
let value = ctx.format_retained(format_args!("{value}"), operation)?;
ctx.insert_btree_map(&mut declared, key, value, operation)?;
}
}
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(DECLARED_CARRIER.len()),
operation,
)?;
let key = cadmpeg_core::text::NonBlankString::new(
ctx.copy_retained_text(DECLARED_CARRIER, operation)?,
)
.ok_or_else(|| cadmpeg_core::CodecError::malformed("empty kernel carrier key"))?;
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(carrier.len()),
operation,
)?;
let value = ctx.copy_retained_text(carrier, operation)?;
ctx.insert_btree_map(&mut declared, key, value, operation)?;
let matched = match_header(header).with_declared(declared);
match instance {
LayerInstance::Sole => Ok(matched),
LayerInstance::Tagged => {
ctx.charge_work(
cadmpeg_core::decode::u64_from_index(carrier.len()),
operation,
)?;
Ok(matched.with_instance(ctx.copy_retained_text(carrier, operation)?))
}
}
}
pub const VERIFIED_ACIS_MAJORS: [u32; 2] = [217, 218];
#[must_use]
pub fn acis_band_verified(save_format_major: Option<u32>) -> bool {
save_format_major.is_some_and(|major| VERIFIED_ACIS_MAJORS.contains(&major))
}
#[must_use]
pub fn nearest_verified_acis(save_format_major: Option<u32>) -> DialectId {
if save_format_major.is_some_and(|major| major >= 218) {
ACIS_SAVE_FORMAT_218
} else {
ACIS_SAVE_FORMAT_217
}
}
#[must_use]
pub fn acis_match(save_format_major: Option<u32>) -> DialectMatch {
let row = acis_binary_row(save_format_major);
if acis_band_verified(save_format_major) {
DialectMatch::admitted(row)
} else {
DialectMatch::unverified(row, Grammar::of(&nearest_verified_acis(save_format_major)))
}
}
#[must_use]
pub fn unverified_message(subject: &str, matched: &DialectMatch) -> Option<String> {
if matched.format() != FORMAT {
return None;
}
let using = match matched.admission() {
Admission::Unverified { .. } => matched.using(),
Admission::Residual => None,
Admission::Admitted | Admission::Refused => return None,
};
let declared = match (
matched.declared().get(DECLARED_SAVE_FORMAT_MAJOR),
matched.declared().get(DECLARED_SAVE_FORMAT_MINOR),
) {
(Some(major), Some(minor)) => format!("save format {major}.{minor}"),
(Some(major), None) => format!("save format major {major}"),
(None, _) => "no save format".to_owned(),
};
Some(using.as_ref().map_or_else(
|| {
format!(
"{subject} declares {declared}; its recovery names no declared save-band grammar as a substitute"
)
},
|using| {
format!(
"{subject} declares {declared}, which no verified Spatial ACIS band declares; its records were read with the grammar `{using}` declares, and what they decoded is reported as it decoded"
)
},
))
}
#[must_use]
pub fn acis_binary_row(save_format_major: Option<u32>) -> DialectId {
match save_format_major {
Some(217) => ACIS_SAVE_FORMAT_217,
Some(218) => ACIS_SAVE_FORMAT_218,
_ => ACIS_SAVE_FORMAT_BINARY_OTHER,
}
}
#[must_use]
pub fn asm_binary_row(width: RefWidth) -> DialectId {
match width {
RefWidth::Four => ACIS_ASM_BINARYFILE_4,
RefWidth::Eight => ACIS_ASM_BINARYFILE_8,
}
}
#[cfg(test)]
mod tests {
use super::{
acis_band_verified, acis_binary_row, acis_match, classify, classify_layer,
nearest_verified_acis, unverified_message, KernelHeaderRef, ACIS_ASM_BINARYFILE_8,
ACIS_SAVE_FORMAT_217, ACIS_SAVE_FORMAT_218, ACIS_SAVE_FORMAT_BINARY_OTHER, ACIS_TEXT_ACIS,
ACIS_TEXT_ASM, ACIS_UNKNOWN, DECLARED_CARRIER, FORMAT,
};
use crate::kernel_header::RefWidth;
use crate::kernel_header::{BinaryHeader, KernelHeader};
use cadmpeg_core::dialect::{Admission, DialectMatch, LayerInstance};
use std::collections::BTreeSet;
fn header(width: RefWidth, save_format_version: Option<u32>) -> BinaryHeader {
BinaryHeader {
width,
metadata: KernelHeader {
save_format_version,
entity_count: None,
flags: None,
product_family: None,
product_version: None,
save_date: None,
scale: None,
linear: None,
angular: None,
},
}
}
#[test]
fn only_the_two_witnessed_majors_are_verified() {
for major in [None, Some(7), Some(216), Some(219), Some(700)] {
assert!(!acis_band_verified(major), "{major:?}");
assert_eq!(acis_binary_row(major), ACIS_SAVE_FORMAT_BINARY_OTHER);
}
assert!(acis_band_verified(Some(217)));
assert!(acis_band_verified(Some(218)));
assert_eq!(acis_binary_row(Some(217)), ACIS_SAVE_FORMAT_217);
assert_eq!(acis_binary_row(Some(218)), ACIS_SAVE_FORMAT_218);
}
#[test]
fn the_nearest_verified_band_is_the_nearer_major() {
for major in [None, Some(7), Some(216), Some(217)] {
assert_eq!(nearest_verified_acis(major), ACIS_SAVE_FORMAT_217);
}
for major in [Some(218), Some(232), Some(700)] {
assert_eq!(nearest_verified_acis(major), ACIS_SAVE_FORMAT_218);
}
}
#[test]
fn admission_folds_the_verified_band_and_nearest_row() {
assert_eq!(
acis_match(Some(217)),
DialectMatch::admitted(ACIS_SAVE_FORMAT_217)
);
let unverified = acis_match(Some(700));
assert_eq!(unverified.dialect(), &ACIS_SAVE_FORMAT_BINARY_OTHER);
assert_eq!(unverified.using(), Some(ACIS_SAVE_FORMAT_218));
assert_eq!(
classify(KernelHeaderRef::TextAcis(
&header(RefWidth::Four, Some(70_000)).metadata
))
.admission(),
&Admission::Residual
);
}
#[test]
fn unverified_message_projects_the_complete_kernel_declaration() {
let binary = classify(KernelHeaderRef::Acis(&header(RefWidth::Four, Some(70_001))));
assert_eq!(
unverified_message("the carrier", &binary).as_deref(),
Some(
"the carrier declares save format 700.1, which no verified Spatial ACIS band declares; its records were read with the grammar `acis:save-format-218` declares, and what they decoded is reported as it decoded"
)
);
let text = classify(KernelHeaderRef::TextAcis(
&header(RefWidth::Four, None).metadata,
));
assert_eq!(
unverified_message("the stream", &text).as_deref(),
Some(
"the stream declares no save format; its recovery names no declared save-band grammar as a substitute"
)
);
assert!(unverified_message(
"the carrier",
&classify(KernelHeaderRef::Acis(&header(RefWidth::Four, Some(21_703))))
)
.is_none());
assert!(unverified_message(
"the carrier",
&DialectMatch::admitted(cadmpeg_core::dialect_id!("test:text"))
)
.is_none());
}
#[test]
fn layer_classification_owns_carrier_identity() {
let header = header(RefWidth::Four, Some(21_804));
let ctx = cadmpeg_test_support::service_decode_context();
let matched = classify_layer(
&ctx,
KernelHeaderRef::Acis(&header),
"stream@12",
LayerInstance::Tagged,
)
.unwrap();
assert_eq!(matched.declared()[DECLARED_CARRIER], "stream@12");
assert_eq!(matched.instance(), Some("stream@12"));
}
#[test]
fn classification_uses_family_and_canonical_declarations() {
let acis = header(RefWidth::Four, Some(21_703));
let matched = classify(KernelHeaderRef::Acis(&acis));
assert_eq!(matched.format(), FORMAT);
assert_eq!(matched.dialect(), &ACIS_SAVE_FORMAT_217);
assert_eq!(matched.admission(), &Admission::Admitted);
assert_eq!(
matched.declared().clone().into_iter().collect::<Vec<_>>(),
[
(
cadmpeg_core::nonblank_literal!("reference_width"),
"4".to_owned()
),
(
cadmpeg_core::nonblank_literal!("save_format_major"),
"217".to_owned()
),
(
cadmpeg_core::nonblank_literal!("save_format_minor"),
"3".to_owned()
),
]
);
let asm = header(RefWidth::Eight, Some(70_001));
assert_eq!(
classify(KernelHeaderRef::Asm(&asm)),
DialectMatch::admitted(ACIS_ASM_BINARYFILE_8).with_declared(
[
(
cadmpeg_core::nonblank_literal!("reference_width"),
"8".to_owned()
),
(
cadmpeg_core::nonblank_literal!("save_format_major"),
"700".to_owned()
),
(
cadmpeg_core::nonblank_literal!("save_format_minor"),
"1".to_owned()
),
]
.into_iter()
.collect(),
)
);
assert_eq!(
classify(KernelHeaderRef::TextAsm(&asm.metadata)).dialect(),
&ACIS_TEXT_ASM
);
assert_eq!(
classify(KernelHeaderRef::TextAcis(&acis.metadata)).dialect(),
&ACIS_TEXT_ACIS
);
let unknown = classify(KernelHeaderRef::Unknown);
assert_eq!(unknown.dialect(), &ACIS_UNKNOWN);
assert_eq!(unknown.admission(), &Admission::Refused);
assert!(unknown.declared().is_empty());
}
#[test]
fn every_acis_registry_row_is_produced() {
let acis = header(RefWidth::Four, Some(21_700));
let asm4 = header(RefWidth::Four, Some(70_000));
let asm8 = header(RefWidth::Eight, Some(70_000));
let ids: BTreeSet<_> = [
classify(KernelHeaderRef::Acis(&acis)),
classify(KernelHeaderRef::Acis(&header(RefWidth::Four, Some(21_800)))),
classify(KernelHeaderRef::Acis(&header(RefWidth::Four, Some(23_200)))),
classify(KernelHeaderRef::Asm(&asm4)),
classify(KernelHeaderRef::Asm(&asm8)),
classify(KernelHeaderRef::TextAsm(&asm8.metadata)),
classify(KernelHeaderRef::TextAcis(&acis.metadata)),
classify(KernelHeaderRef::Unknown),
]
.into_iter()
.map(|matched| matched.dialect().to_string())
.collect();
assert_eq!(
ids,
cadmpeg_test_support::registry_ids(FORMAT).expect("identity registry parses")
);
}
#[test]
fn kernel_layer_preserves_declaration_and_carrier_refusals() {
for (retained, collections, expected) in [
(
0,
u64::MAX,
cadmpeg_core::decode::ResourceDimension::RetainedBytes,
),
(
u64::MAX,
0,
cadmpeg_core::decode::ResourceDimension::CollectionItems,
),
(
7,
u64::MAX,
cadmpeg_core::decode::ResourceDimension::RetainedBytes,
),
(
16,
u64::MAX,
cadmpeg_core::decode::ResourceDimension::RetainedBytes,
),
] {
let arena = cadmpeg_core::decode::DecodeArena::new();
let mut policy = cadmpeg_core::decode::DecodePolicy::service();
policy.limits.max_retained_bytes = retained;
policy.limits.max_collection_items = collections;
let (ctx, _) =
cadmpeg_core::decode::DecodeContext::from_root_bytes(&[], &arena, &policy).unwrap();
let error = classify_layer(
&ctx,
KernelHeaderRef::Unknown,
"stream@12",
LayerInstance::Tagged,
)
.unwrap_err();
assert!(
matches!(error, cadmpeg_core::CodecError::ResourceLimit(failure)
if failure.dimension == expected && failure.operation == "retain kernel dialect declarations")
);
}
}
}