use structured_asn1::{Encoding, ErrorCode as AsnError, Limit, Limits, Parser};
use structured_pkix::{der, general_name, temporal};
fn memory() -> structured_asn1::memory::Memory {
structured_asn1::memory::Memory::new(std::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(1024 * 1024, 1024 * 1024),
))
}
#[test]
fn public_mailbox_decode_needs_no_storage_and_uses_current_work() {
use structured_asn1::memory::{Memory, MemoryAccount};
for (mailbox, valid) in [
("\"\"@example.ua", true),
("\"a@b\"@localhost", true),
("user@[IPv6:::ffff:012.0.2.1]", true),
("user@[AS400:QSYS]", true),
("user(comment)@example.ua", false),
("user@[256.0.0.1]", false),
] {
let mut bytes = vec![0x81, u8::try_from(mailbox.len()).unwrap()];
bytes.extend_from_slice(mailbox.as_bytes());
let parser = Parser::der(memory());
let mut reader = parser.read(&bytes, 401).unwrap();
let element = reader.next_element().unwrap().unwrap();
let execution = Memory::new(std::sync::Arc::new(MemoryAccount::new(0, mailbox.len())));
let reader = reader.with_execution(&execution);
match general_name::read(&reader, &element, "mailbox") {
Ok(general_name::GeneralName::Email(value)) if valid => {
assert_eq!(value, mailbox);
assert_eq!(value.as_ptr(), element.contents().as_ptr());
}
Err(error) if !valid => {
assert_eq!(error.code(), structured_pkix::ErrorCode::MalformedValue);
}
other => panic!("unexpected mailbox result: {other:?}"),
}
assert_eq!(execution.used(), 0);
assert_eq!(execution.work_used(), mailbox.len());
let error = general_name::read(&reader, &element, "mailbox").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.field(), "mailbox");
assert_eq!(execution.work_used(), mailbox.len());
}
}
#[test]
fn public_set_ordering_refuses_an_unfunded_retained_comparison() {
use structured_asn1::memory::{Memory, MemoryAccount};
let bytes = b"\x04\x02ab";
let parser = Parser::der(memory());
let mut reader = parser.read(bytes, 101).unwrap();
let member = reader.next_element().unwrap().unwrap();
let execution = Memory::new(std::sync::Arc::new(MemoryAccount::new(1024, 0)));
let reader = reader.with_execution(&execution);
let result = der::set_ordered(&reader, bytes, &member, "signedAttrs");
let error = result.unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.offset(), 101);
assert_eq!(error.field(), "signedAttrs");
assert_eq!(execution.work_used(), 0);
}
#[test]
fn public_set_ordering_keeps_padding_semantics_and_reports_false_separately() {
let bytes = b"\x04\x02ab";
for (previous, expected) in [
(&[][..], true),
(&bytes[..], true),
(&b"\x04\x02ab\x00"[..], true),
(&b"\x04\x02ac"[..], false),
] {
let parser = Parser::der(memory());
let mut reader = parser.read(bytes, 101).unwrap();
let member = reader.next_element().unwrap().unwrap();
assert_eq!(
der::set_ordered(&reader, previous, &member, "signedAttrs").unwrap(),
expected
);
}
}
#[test]
fn public_set_ordering_preserves_the_parser_local_cumulative_ceiling() {
let bytes = b"\x04\x02ab";
let execution = memory();
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: 7,
..Limits::default()
},
execution.clone(),
);
let mut reader = parser.read(bytes, 101).unwrap();
let member = reader.next_element().unwrap().unwrap();
assert!(der::set_ordered(&reader, bytes, &member, "signedAttrs").unwrap());
let before = execution.work_used();
let error = der::set_ordered(&reader, bytes, &member, "signedAttrs").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.offset(), 101);
assert_eq!(error.field(), "signedAttrs");
assert_eq!(execution.work_used(), before);
}
#[test]
fn public_temporal_decode_uses_the_current_execution_account() {
use structured_asn1::memory::{Memory, MemoryAccount};
let bytes = b"\x18\x1920161231235960.123456789Z";
let parser = Parser::der(memory());
let mut reader = parser.read(bytes, 201).unwrap();
let element = reader.next_element().unwrap().unwrap();
let execution = Memory::new(std::sync::Arc::new(MemoryAccount::new(1024, 0)));
let reader = reader.with_execution(&execution);
let error = temporal::read_generalized(&reader, &element, "genTime").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.offset(), 201);
assert_eq!(error.field(), "genTime");
assert_eq!(execution.work_used(), 0);
}
#[test]
fn public_temporal_decode_admits_exact_work_and_preserves_both_ceilings() {
use structured_asn1::memory::{Memory, MemoryAccount};
let bytes = b"\x18\x1920161231235960.123456789Z";
for (local, aggregate) in [(u64::MAX, 53), (53, 1024)] {
let execution = Memory::new(std::sync::Arc::new(MemoryAccount::new(1024, aggregate)));
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: local,
..Limits::default()
},
execution.clone(),
);
let mut reader = parser.read(bytes, 201).unwrap();
let element = reader.next_element().unwrap().unwrap();
let before = execution.work_used();
let reading = temporal::read_generalized(&reader, &element, "genTime").unwrap();
assert!(reading.start().is_leap_second());
assert_eq!(reading.start().nanos(), 123_456_789);
assert_eq!(
execution.work_used() - before,
2 * element.contents().len() + 1
);
let before = execution.work_used();
let error = temporal::read_generalized(&reader, &element, "genTime").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.offset(), 201);
assert_eq!(execution.work_used(), before);
}
}
#[test]
fn der_fraction_validation_admits_the_semantic_decimal_passes() {
let bytes = b"\x18\x1920161231235960.123456789Z";
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: 27,
..Limits::default()
},
memory(),
);
let mut reader = parser.read(bytes, 201).unwrap();
let element = reader.next_element().unwrap().unwrap();
let error = der::conforms(&reader, &element, "genTime").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
}
#[test]
fn generalized_time_der_and_temporal_paths_agree_at_leap_boundary() {
let bytes = b"\x18\x1a20161231235960.1234567891Z";
let parser = Parser::der(memory());
let mut reader = parser.read(bytes, 51).unwrap();
let element = reader.next_element().unwrap().unwrap();
der::conforms(&reader, &element, "timestamp").unwrap();
let reading = temporal::read_generalized(&reader, &element, "timestamp").unwrap();
assert!(reading.start().is_leap_second());
assert_eq!(reading.start().nanos(), 123_456_789);
assert!(reading.has_subnanosecond_remainder());
assert_eq!(element.as_bytes().as_ptr(), bytes.as_ptr());
assert_eq!(element.span().start, 51);
}
#[test]
fn public_general_name_borrows_bytes_and_shares_work_budget() {
let bytes = b"\x82\x0aexample.ua";
let parser = Parser::new(
Encoding::Der,
Limits {
max_work: 12,
..Limits::default()
},
memory(),
);
let mut reader = parser.read(bytes, 71).unwrap();
let element = reader.next_element().unwrap().unwrap();
let general_name::GeneralName::Dns(name) =
general_name::read(&reader, &element, "tsa").unwrap()
else {
panic!("expected DNS name");
};
assert_eq!(name, "example.ua");
assert_eq!(name.as_ptr(), element.contents().as_ptr());
let error = general_name::read(&reader, &element, "tsa").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Work))
);
assert_eq!(error.field(), "tsa");
}
#[test]
fn public_general_name_cannot_reset_nested_depth() {
let bytes = b"\xa4\x02\x30\x00";
let parser = Parser::new(
Encoding::Der,
Limits {
max_depth: 1,
..Limits::default()
},
memory(),
);
let mut reader = parser.read_nested(bytes, 91, Encoding::Der, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let error = general_name::read(&reader, &element, "tsa").unwrap_err();
assert_eq!(
error.code(),
structured_pkix::ErrorCode::Encoding(AsnError::LimitExceeded(Limit::Depth))
);
assert_eq!(error.offset(), element.contents_span().start);
}
#[test]
fn public_name_results_retain_storage_and_use_the_current_execution_domain() {
use structured_asn1::memory::{Memory, MemoryAccount};
let source = memory();
let execution = memory();
let exhausted = Memory::new(std::sync::Arc::new(MemoryAccount::new(1024, 0)));
let text = {
let parser = Parser::der(source.clone());
let bytes = b"\x30\x12\x31\x10\x30\x0e\x06\x03\x55\x04\x03\x0c\x07Subject";
let mut reader = parser.read(bytes, 91).unwrap();
let element = reader.next_element().unwrap().unwrap();
let name = structured_pkix::name::read(&reader, &element, "issuer").unwrap();
let prepared = name.ordered(&execution).unwrap();
assert!(execution.used() > 0);
let work = source.work_used();
let error = prepared.order(&prepared, &exhausted).unwrap_err();
assert!(error.is_resource());
assert_eq!(error.offset(), 91);
assert_eq!(source.work_used(), work);
name.common_name(&execution)
.unwrap()
.unwrap()
.text(&execution)
.unwrap()
.unwrap()
};
assert_eq!(source.used(), 0);
assert_eq!(execution.used(), text.capacity());
assert_eq!(text.as_str(&execution).unwrap(), "Subject");
assert!(text.as_str(&exhausted).is_err());
drop(text);
assert_eq!(execution.used(), 0);
}