use std::sync::Arc;
use structured_asn1::{
Parser,
memory::{Memory, MemoryAccount},
};
use structured_cms::{ErrorCode, timestamp::TokenExtensions};
fn memory() -> Memory {
Memory::new(Arc::new(MemoryAccount::new(65536, 65536)))
}
fn tlv(tag: u8, bytes: &[u8]) -> Vec<u8> {
let mut output = vec![tag, u8::try_from(bytes.len()).unwrap()];
output.extend_from_slice(bytes);
output
}
fn extension(kind: u8, critical: Option<bool>, value: &[u8], trailing: &[u8]) -> Vec<u8> {
let mut bytes = vec![6, 1, kind];
if let Some(flag) = critical {
bytes.extend_from_slice(&[1, 1, if flag { 0xff } else { 0 }]);
}
bytes.extend_from_slice(value);
bytes.extend_from_slice(trailing);
tlv(0x30, &bytes)
}
#[test]
fn equal_assembled_values_retain_first_original_extension_and_one_duplicate_fault() {
let first = extension(42, None, &[4, 2, b'a', b'b'], &[]);
let second = extension(42, None, &[0x24, 6, 4, 1, b'a', 4, 1, b'b'], &[]);
let mut members = first.clone();
members.extend_from_slice(&second);
let bytes = tlv(0xa1, &members);
let memory = memory();
let parser = Parser::ber(memory.clone());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let extensions = TokenExtensions::read(&parent, &element).unwrap();
assert_eq!(extensions.entries().len(), 1);
assert_eq!(extensions.entries()[0].value().as_one(), Some(&b"ab"[..]));
assert_eq!(
extensions.entries()[0].value().as_one().unwrap().as_ptr(),
bytes[9..].as_ptr()
);
assert_eq!(extensions.faults().len(), 1);
assert_eq!(extensions.faults()[0].code(), ErrorCode::DuplicateField);
assert_eq!(
extensions.faults()[0].offset(),
402 + u64::try_from(first.len()).unwrap()
);
drop(extensions);
assert_eq!(memory.used(), 0);
}
#[test]
fn conflicting_criticality_or_value_refuses_without_partial_catalogue() {
let first = extension(42, None, &[4, 1, b'a'], &[]);
for second in [
extension(42, Some(true), &[4, 1, b'a'], &[]),
extension(42, None, &[4, 1, b'b'], &[]),
] {
let mut members = first.clone();
members.extend(second);
let bytes = tlv(0xa1, &members);
let memory = memory();
let parser = Parser::ber(memory.clone());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let error = TokenExtensions::read(&parent, &element).unwrap_err();
assert_eq!(error.code(), ErrorCode::DuplicateField);
assert_eq!(error.offset(), 402 + u64::try_from(first.len()).unwrap());
assert_eq!(memory.used(), 0);
}
}
#[test]
fn default_fault_precedes_broken_value_and_later_members_remain_visible() {
let broken = extension(42, Some(false), &[4, 0x82, 1], &[]);
let valid = extension(43, None, &[4, 1, b'c'], &[5, 0]);
let mut members = broken.clone();
members.extend(valid);
let bytes = tlv(0xa1, &members);
let memory = memory();
let parser = Parser::ber(memory.clone());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let extensions = TokenExtensions::read(&parent, &element).unwrap();
assert_eq!(extensions.entries().len(), 1);
assert_eq!(extensions.entries()[0].kind().as_bytes(), &[43]);
assert_eq!(extensions.entries()[0].value().as_one(), Some(&b"c"[..]));
assert_eq!(extensions.faults().len(), 3);
assert_eq!(extensions.faults()[0].code(), ErrorCode::DefaultWritten);
assert_eq!(extensions.faults()[0].offset(), 407);
assert!(!extensions.faults()[1].is_terminal());
assert_eq!(extensions.faults()[2].code(), ErrorCode::TrailingData);
drop(extensions);
assert_eq!(memory.used(), 0);
}
#[test]
fn empty_extensions_are_a_readable_size_fault_not_successful_conformance() {
let bytes = [0xa1, 0];
let memory = memory();
let parser = Parser::ber(memory.clone());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let extensions = TokenExtensions::read(&parent, &element).unwrap();
assert!(extensions.entries().is_empty());
assert_eq!(extensions.faults().len(), 1);
assert_eq!(extensions.faults()[0].code(), ErrorCode::MalformedValue);
assert_eq!(extensions.faults()[0].offset(), 400);
drop(extensions);
assert_eq!(memory.used(), 0);
}
#[test]
fn critical_recognition_preserves_unknowns_and_original_callback_cause() {
let mut members = extension(42, None, &[4, 1, 7], &[]);
members.extend(extension(43, Some(true), &[4, 1, 8], &[]));
members.extend(extension(44, Some(true), &[4, 1, 9], &[]));
let bytes = tlv(0xa1, &members);
let parser = Parser::ber(memory());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let extensions = TokenExtensions::read(&parent, &element).unwrap();
let mut calls = Vec::new();
let unknown = extensions
.unrecognized_critical(&parent, |entry, _| {
calls.push(entry.kind().as_bytes()[0]);
Ok::<_, &'static str>(entry.kind().as_bytes() == [43])
})
.unwrap();
assert_eq!(calls, [43, 44]);
assert_eq!(unknown.len(), 1);
assert_eq!(unknown[0].as_bytes(), &[44]);
let failure = extensions
.unrecognized_critical(&parent, |_, _| Err::<bool, _>("handler-resource"))
.unwrap_err();
let structured_cms::timestamp::RecognitionError::Callback { span, source } = failure else {
panic!("callback cause lost");
};
assert_eq!(source, "handler-resource");
assert_eq!(span, extensions.entries()[1].span());
}
#[test]
fn critical_recognition_foreign_or_exhausted_query_never_calls_handler() {
let bytes = tlv(0xa1, &extension(42, Some(true), &[4, 1, 7], &[]));
let account = memory();
let parser = Parser::ber(account.clone());
let mut parent = parser.read(&bytes, 400).unwrap();
let element = parent.next_element().unwrap().unwrap();
let extensions = TokenExtensions::read(&parent, &element).unwrap();
let other = Parser::ber(account.clone());
let foreign = other.read(&[], 900).unwrap();
let mut calls = 0;
let failure = extensions
.unrecognized_critical(&foreign, |_, _| {
calls += 1;
Ok::<_, ()>(true)
})
.unwrap_err();
assert!(matches!(
failure,
structured_cms::timestamp::RecognitionError::Processing(_)
));
assert_eq!(calls, 0);
let exhausted = Parser::new(
structured_asn1::Encoding::Ber,
structured_asn1::Limits {
max_work: 1,
..structured_asn1::Limits::default()
},
account,
);
let query = exhausted.read(&[], 900).unwrap();
let parent = parent.with_query(&query).unwrap();
let failure = extensions
.unrecognized_critical(&parent, |_, _| {
calls += 1;
Ok::<_, ()>(true)
})
.unwrap_err();
let structured_cms::timestamp::RecognitionError::Processing(source) = failure else {
panic!("refusal became recognition");
};
assert!(source.is_terminal());
assert_eq!(source.offset(), extensions.entries()[0].span().start);
assert_eq!(calls, 0);
}