use crate::error::{Error, ErrorCode, Located, Result};
use crate::oid;
#[cfg(test)]
use alloc::vec::Vec;
use structured_asn1::memory::{Memory, OwnedString, OwnedVec, Work};
use structured_asn1::{Class, Element, Oid, ScopedReader as Reader, Tag, universal};
use unicode_bidi::BidiClass;
mod fold;
mod normalize;
mod prepared;
mod render;
pub(crate) mod segments;
mod selector;
pub use selector::Selector;
pub(crate) use selector::attribute_type_syntax_fits;
pub(crate) use selector::syntax_fits as dn_syntax_fits;
mod unassigned;
#[derive(Debug)]
pub struct Name<'a> {
der: &'a [u8],
parts: OwnedVec<RelativeName<'a>>,
offset: u64,
field: &'static str,
}
#[derive(Debug)]
pub struct RelativeName<'a> {
attributes: OwnedVec<Attribute<'a>>,
}
#[derive(Debug, Clone, Copy)]
pub struct Attribute<'a> {
kind: Oid<'a>,
value: AttributeValue<'a>,
}
#[derive(Debug, Clone, Copy)]
pub struct AttributeValue<'a> {
tag: Tag,
contents: &'a [u8],
der: &'a [u8],
offset: u64,
depth: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Encoding {
Utf8,
Printable,
Ia5,
Visible,
Numeric,
Teletex,
Bmp,
Universal,
Other,
}
impl<'a> Name<'a> {
#[must_use]
pub fn as_bytes(&self) -> &'a [u8] {
self.der
}
#[must_use]
pub fn parts(&self) -> &[RelativeName<'a>] {
&self.parts
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn try_copy(&self, execution: &Memory) -> Result<Self> {
let mut parts = execution.vector();
for part in &self.parts {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, self.field, self.offset))?;
parts
.try_push(part.try_copy(execution)?)
.map_err(|reason| Error::resource(reason, self.field, self.offset))?;
}
Ok(Self {
der: self.der,
parts,
offset: self.offset,
field: self.field,
})
}
pub fn attributes(&self) -> impl Iterator<Item = &Attribute<'a>> {
self.parts.iter().flat_map(RelativeName::attributes)
}
pub fn attributes_of<'n, 'q, W: structured_asn1::memory::Work>(
&'n self,
kind: Oid<'q>,
execution: &'q W,
) -> impl Iterator<Item = Result<&'n Attribute<'a>>> + 'q
where
'n: 'q,
{
let mut attributes = self.attributes();
let mut failed = false;
core::iter::from_fn(move || {
if failed {
return None;
}
for attribute in attributes.by_ref() {
let work = 1 + attribute.kind.as_bytes().len().max(kind.as_bytes().len()) as u64;
if let Err(reason) = execution.charge_work(work) {
failed = true;
return Some(Err(self.resource(reason)));
}
if attribute.kind == kind {
return Some(Ok(attribute));
}
}
None
})
}
pub fn attribute<W: structured_asn1::memory::Work>(
&self,
kind: Oid<'_>,
execution: &W,
) -> Result<Option<&Attribute<'a>>> {
self.attributes_of(kind, execution).next().transpose()
}
pub fn common_name<W: structured_asn1::memory::Work>(
&self,
execution: &W,
) -> Result<Option<&Attribute<'a>>> {
self.attribute(oid::COMMON_NAME, execution)
}
pub fn organization_identifier<W: structured_asn1::memory::Work>(
&self,
execution: &W,
) -> Result<Option<&Attribute<'a>>> {
self.attribute(oid::ORGANIZATION_IDENTIFIER, execution)
}
pub fn matches<W: structured_asn1::memory::Work>(
&self,
other: &Name<'_>,
execution: &W,
) -> Result<bool> {
self.matches_with(other, execution, None)
}
pub fn matches_in(&self, other: &Name<'_>, parent: &Reader<'_, '_>) -> Result<bool> {
self.matches_with(other, &parent.execution(), Some(parent))
}
pub(crate) fn matches_with<W: structured_asn1::memory::Work>(
&self,
other: &Name<'_>,
execution: &W,
parent: Option<&Reader<'_, '_>>,
) -> Result<bool> {
if self.parts.len() != other.parts.len() {
return Ok(false);
}
if self.der.len() == other.der.len() {
execution
.charge_work(self.der.len() as u64)
.map_err(|reason| self.resource(reason))?;
if self.der == other.der {
return Ok(true);
}
}
Ok(self.order_with(other, execution, parent)? == core::cmp::Ordering::Equal)
}
pub(crate) fn offset(&self) -> u64 {
self.offset
}
pub(crate) fn field(&self) -> &'static str {
self.field
}
pub(crate) fn resource(&self, reason: structured_asn1::memory::MemoryError) -> Error {
Error::resource(reason, self.field, self.offset)
}
pub fn order<W: structured_asn1::memory::Work>(
&self,
other: &Name<'_>,
execution: &W,
) -> Result<core::cmp::Ordering> {
self.order_with(other, execution, None)
}
fn order_with<W: structured_asn1::memory::Work>(
&self,
other: &Name<'_>,
execution: &W,
parent: Option<&Reader<'_, '_>>,
) -> Result<core::cmp::Ordering> {
let order = self.parts.len().cmp(&other.parts.len());
if order != core::cmp::Ordering::Equal {
return Ok(order);
}
for (left, right) in self.parts.iter().zip(&other.parts) {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, self.field, self.offset))?;
let order = prepared::compare_parts_in(left, right, execution, parent)?;
if order != core::cmp::Ordering::Equal {
return Ok(order);
}
}
Ok(core::cmp::Ordering::Equal)
}
pub fn ordered<W: structured_asn1::memory::Work>(
&self,
execution: &W,
) -> Result<Ordered<'a, W>> {
Ok(Ordered {
prepared: prepared::PreparedName::of(&self.parts, execution)?,
offset: self.offset,
})
}
pub fn ordered_in<'p>(
&self,
parent: &'p Reader<'_, '_>,
) -> Result<Ordered<'a, structured_asn1::memory::Execution<'p>>> {
Ok(Ordered {
prepared: prepared::PreparedName::of_in(
&self.parts,
&parent.execution(),
Some(parent),
)?,
offset: self.offset,
})
}
pub fn starts_with<W: structured_asn1::memory::Work>(
&self,
base: &Name<'_>,
execution: &W,
) -> Result<bool> {
if base.parts.len() > self.parts.len() {
return Ok(false);
}
for (left, right) in self.parts.iter().zip(&base.parts) {
execution
.charge_work(1)
.map_err(|reason| self.resource(reason))?;
let order = prepared::compare_parts(left, right, execution)?;
if order != core::cmp::Ordering::Equal {
return Ok(false);
}
}
Ok(true)
}
}
#[derive(Debug)]
pub struct Ordered<'a, W: structured_asn1::memory::Work = Memory> {
prepared: prepared::PreparedName<'a, W>,
offset: u64,
}
impl<W: structured_asn1::memory::Work> Ordered<'_, W> {
pub fn order<V: structured_asn1::memory::Work, E: structured_asn1::memory::Work>(
&self,
other: &Ordered<'_, V>,
execution: &E,
) -> Result<core::cmp::Ordering> {
self.prepared
.order(&other.prepared, execution)
.map_err(|reason| Error::resource(reason, "name", self.offset))
}
}
pub(crate) fn matches_composed<const ENCODED: bool>(
left: &Name<'_>,
left_suffix: Option<&RelativeName<'_>>,
right: &Name<'_>,
right_suffix: Option<&RelativeName<'_>>,
execution: &impl structured_asn1::memory::Work,
) -> Result<bool> {
if left_suffix.is_none() && right_suffix.is_none() {
if ENCODED {
return encoded_bytes_match(left.der, right.der, left, execution);
}
return left.matches(right, execution);
}
if ENCODED && let (Some(left_part), Some(right_part)) = (left_suffix, right_suffix) {
if !left.matches(right, execution)? {
return Ok(false);
}
return encoded_parts_match(left_part, right_part, left, execution);
}
let left_len = left
.parts
.len()
.checked_add(usize::from(left_suffix.is_some()))
.ok_or_else(|| left.resource(structured_asn1::memory::MemoryError::WorkExceeded))?;
let right_len = right
.parts
.len()
.checked_add(usize::from(right_suffix.is_some()))
.ok_or_else(|| right.resource(structured_asn1::memory::MemoryError::WorkExceeded))?;
if left_len != right_len {
return Ok(false);
}
for index in 0..left_len {
execution
.charge_work(1)
.map_err(|reason| left.resource(reason))?;
let left_part = if index < left.parts.len() {
&left.parts[index]
} else {
left_suffix.expect("suffix was counted")
};
let right_part = if index < right.parts.len() {
&right.parts[index]
} else {
right_suffix.expect("suffix was counted")
};
let matches = if ENCODED {
encoded_parts_match(left_part, right_part, left, execution)?
} else {
prepared::compare_parts(left_part, right_part, execution)? == core::cmp::Ordering::Equal
};
if !matches {
return Ok(false);
}
}
Ok(true)
}
fn encoded_bytes_match(
left: &[u8],
right: &[u8],
source: &Name<'_>,
execution: &impl structured_asn1::memory::Work,
) -> Result<bool> {
let work = u64::try_from(left.len().max(right.len()))
.ok()
.and_then(|n| n.checked_mul(2))
.and_then(|n| n.checked_add(1))
.ok_or_else(|| source.resource(structured_asn1::memory::MemoryError::WorkExceeded))?;
execution
.charge_work(work)
.map_err(|reason| source.resource(reason))?;
Ok(left == right)
}
fn encoded_parts_match(
left: &RelativeName<'_>,
right: &RelativeName<'_>,
source: &Name<'_>,
execution: &impl structured_asn1::memory::Work,
) -> Result<bool> {
if left.attributes.len() != right.attributes.len() {
return Ok(false);
}
for (left, right) in left.attributes.iter().zip(right.attributes.iter()) {
execution
.charge_work(1)
.map_err(|reason| source.resource(reason))?;
if !encoded_bytes_match(
left.kind.as_bytes(),
right.kind.as_bytes(),
source,
execution,
)? || !encoded_bytes_match(left.value.der, right.value.der, source, execution)?
{
return Ok(false);
}
}
Ok(true)
}
impl<'a> RelativeName<'a> {
#[must_use]
pub fn attributes(&self) -> &[Attribute<'a>] {
&self.attributes
}
pub fn try_copy(&self, execution: &Memory) -> Result<Self> {
let mut attributes = execution.vector();
attributes
.try_extend_from_slice(&self.attributes)
.map_err(|reason| {
Error::resource(reason, "name.relative", self.attributes[0].value.offset)
})?;
Ok(Self { attributes })
}
#[must_use]
#[cfg(test)]
fn matches(&self, other: &RelativeName<'_>) -> bool {
if self.attributes.len() != other.attributes.len() {
return false;
}
if let ([left], [right]) = (&self.attributes[..], &other.attributes[..]) {
return left.matches(right);
}
let mut ours: Vec<&Attribute<'a>> = self.attributes.iter().collect();
let mut theirs: Vec<&Attribute<'_>> = other.attributes.iter().collect();
ours.sort_unstable_by(|left, right| left.order(right));
theirs.sort_unstable_by(|left, right| left.order(right));
ours.iter()
.zip(&theirs)
.all(|(left, right)| left.matches(right))
}
#[cfg(test)]
fn order(&self, other: &RelativeName<'_>) -> core::cmp::Ordering {
self.attributes
.len()
.cmp(&other.attributes.len())
.then_with(|| {
if let ([left], [right]) = (&self.attributes[..], &other.attributes[..]) {
return left.order(right);
}
let mut ours: Vec<&Attribute<'a>> = self.attributes.iter().collect();
let mut theirs: Vec<&Attribute<'_>> = other.attributes.iter().collect();
ours.sort_unstable_by(|left, right| left.order(right));
theirs.sort_unstable_by(|left, right| left.order(right));
ours.iter()
.zip(&theirs)
.map(|(left, right)| left.order(right))
.find(|ordering| ordering.is_ne())
.unwrap_or(core::cmp::Ordering::Equal)
})
}
}
fn syntax_of(kind: Oid<'_>) -> Option<Syntax> {
const DIRECTORY: &[Oid<'static>] = &[
oid::COMMON_NAME,
oid::SURNAME,
oid::LOCALITY,
oid::STATE_OR_PROVINCE,
oid::STREET_ADDRESS,
oid::ORGANIZATION,
oid::ORGANIZATIONAL_UNIT,
oid::TITLE,
oid::GIVEN_NAME,
oid::INITIALS,
oid::GENERATION_QUALIFIER,
oid::PSEUDONYM,
oid::ORGANIZATION_IDENTIFIER,
oid::USER_ID,
];
if DIRECTORY.contains(&kind) {
Some(Syntax::Directory)
} else if kind == oid::COUNTRY || kind == oid::SERIAL_NUMBER || kind == oid::DN_QUALIFIER {
Some(Syntax::Printable)
} else if kind == oid::EMAIL_ADDRESS || kind == oid::DOMAIN_COMPONENT {
Some(Syntax::Ia5)
} else {
None
}
}
#[derive(Clone, Copy)]
enum Syntax {
Directory,
Printable,
Ia5,
}
impl Syntax {
fn permits(self, encoding: Encoding) -> bool {
match self {
Self::Directory => matches!(
encoding,
Encoding::Utf8
| Encoding::Printable
| Encoding::Teletex
| Encoding::Bmp
| Encoding::Universal
),
Self::Printable => encoding == Encoding::Printable,
Self::Ia5 => encoding == Encoding::Ia5,
}
}
}
fn permits(kind: Oid<'_>, encoding: Encoding) -> bool {
syntax_of(kind).is_some_and(|syntax| syntax.permits(encoding))
}
fn size_bounds(kind: Oid<'_>) -> (usize, usize) {
if kind == oid::COUNTRY {
(2, 2)
} else if kind == oid::COMMON_NAME
|| kind == oid::ORGANIZATION
|| kind == oid::ORGANIZATIONAL_UNIT
|| kind == oid::TITLE
|| kind == oid::SERIAL_NUMBER
{
(1, 64)
} else if kind == oid::LOCALITY || kind == oid::STATE_OR_PROVINCE {
(1, 128)
} else if kind == oid::EMAIL_ADDRESS {
(1, 255)
} else if kind == oid::PSEUDONYM {
(1, 128)
} else if kind == oid::STREET_ADDRESS {
(1, 128)
} else if kind == oid::SURNAME
|| kind == oid::GIVEN_NAME
|| kind == oid::INITIALS
|| kind == oid::GENERATION_QUALIFIER
{
(1, 32768)
} else {
(1, usize::MAX)
}
}
pub(crate) fn well_formed(attribute: &Attribute<'_>) -> bool {
inside_syntax(attribute, Asking::Shape, |_| true)
}
pub(crate) fn well_formed_in(
attribute: &Attribute<'_>,
parent: &Reader<'_, '_>,
field: &'static str,
) -> Result<bool> {
let work = (attribute.kind.as_bytes().len() as u64)
.checked_mul(64)
.and_then(|kinds| {
(attribute.value.contents.len() as u64)
.checked_mul(2)
.and_then(|contents| kinds.checked_add(contents))
})
.and_then(|work| work.checked_add(1))
.ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
field,
attribute.value.offset,
)
})?;
parent.charge(work).field(field)?;
let value = attribute.value;
if value.tag.is_constructed()
&& value.tag.class() == Class::Universal
&& value.tag.number().is_some_and(segmented_universal_type)
&& value.tag.number() != Some(universal::BIT_STRING)
&& syntax_of(attribute.kind).is_some()
{
debug_assert!(value.depth > 0);
let mut reader = parent
.parser()
.read_nested_query_in(
parent,
value.der,
value.offset,
structured_asn1::Encoding::Ber,
value.depth - 1,
)
.field(field)?;
let element = reader
.next_element()
.field(field)?
.expect("retained complete attribute value");
let execution = parent.execution();
let mut contents = execution.vector();
segments::walk_in(
&reader,
&element,
field,
false,
Some(&mut contents),
&execution,
)?;
parent
.charge((contents.len() as u64).checked_mul(2).ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
field,
value.offset,
)
})?)
.field(field)?;
return Ok(well_formed(&Attribute {
kind: attribute.kind,
value: AttributeValue {
tag: Tag::primitive(value.tag.number().expect("universal string")),
contents: &contents,
..value
},
}));
}
Ok(well_formed(attribute))
}
const DISPLAY_CEILING: usize = 32_768;
#[cfg(test)]
fn preparable(attribute: &Attribute<'_>) -> bool {
if !inside_syntax(attribute, Asking::Folding, |c| !prohibited(c)) {
return false;
}
let mut direction = Direction::default();
for c in normalized(attribute.value.decoded_chars()) {
direction.see(c);
}
direction.settled()
}
#[derive(Default)]
struct Direction {
first: Option<bool>,
last: bool,
any_right: bool,
any_left: bool,
}
impl Direction {
fn see(&mut self, c: char) {
if mapped_to_nothing(c) || c.is_whitespace() {
return;
}
let class = unicode_bidi::bidi_class(c);
let right = matches!(class, BidiClass::R | BidiClass::AL);
if self.first.is_none() {
self.first = Some(right);
}
self.last = right;
self.any_right |= right;
self.any_left |= class == BidiClass::L;
}
fn settled(&self) -> bool {
!self.any_right || (!self.any_left && self.first == Some(true) && self.last)
}
}
fn prohibited(c: char) -> bool {
let point = u32::from(c);
unassigned::in_unicode_3_2(c)
|| matches!(c,
'\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' | '\u{100000}'..='\u{10fffd}'
| '\u{fdd0}'..='\u{fdef}'
| '\u{fffd}')
|| point & 0xfffe == 0xfffe
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Asking {
Shape,
Folding,
}
fn inside_syntax(
attribute: &Attribute<'_>,
asking: Asking,
mut keep: impl FnMut(char) -> bool,
) -> bool {
match syntax_of(attribute.kind) {
Some(syntax) => {
let (least, schema) = size_bounds(attribute.kind);
let most = schema;
if attribute.value.contents().len() > most.saturating_mul(4) {
return false;
}
if !syntax.permits(attribute.value.encoding()) {
return false;
}
if attribute.value.encoding() == Encoding::Teletex && asking == Asking::Shape {
return (least..=most).contains(&attribute.value.contents().len());
}
let country = attribute.kind == oid::COUNTRY;
attribute
.value
.validated_count(|c| (!country || c.is_ascii_alphabetic()) && keep(c))
.is_some_and(|count| (least..=most).contains(&count))
}
None => true,
}
}
fn mapped_to_nothing(c: char) -> bool {
matches!(
c,
'\u{00ad}'
| '\u{1806}'
| '\u{034f}'
| '\u{180b}'..='\u{180d}'
| '\u{fe00}'..='\u{fe0f}'
| '\u{fffc}'
| '\u{0000}'..='\u{0008}'
| '\u{000e}'..='\u{001f}'
| '\u{007f}'..='\u{0084}'
| '\u{0086}'..='\u{009f}'
| '\u{06dd}'
| '\u{070f}'
| '\u{180e}'
| '\u{200b}'..='\u{200f}'
| '\u{202a}'..='\u{202e}'
| '\u{2060}'..='\u{2063}'
| '\u{206a}'..='\u{206f}'
| '\u{feff}'
| '\u{fff9}'..='\u{fffb}'
| '\u{1d173}'..='\u{1d17a}'
| '\u{e0001}'
| '\u{e0020}'..='\u{e007f}'
)
}
#[cfg(test)]
type Canonical<I> = Spaced<Mapped<Recomposed<Mapped<I>>>>;
#[cfg(test)]
type Recomposed<I> = unicode_normalization::Recompositions<I>;
#[cfg(test)]
fn canonical<I: Iterator<Item = char>>(inner: I) -> Canonical<I> {
Spaced::over(Mapped::over(normalized(inner)))
}
#[cfg(test)]
fn normalized<I: Iterator<Item = char>>(inner: I) -> Recomposed<Mapped<I>> {
use unicode_normalization::UnicodeNormalization;
Mapped::over(inner).nfkc()
}
struct Mapped<I, const FOLD_CASE: bool = true> {
inner: I,
folded: Option<Folded>,
}
enum Folded {
Mapped(core::str::Chars<'static>),
Lowered(core::char::ToLowercase),
}
impl Iterator for Folded {
type Item = char;
fn next(&mut self) -> Option<char> {
match self {
Self::Mapped(rest) => rest.next(),
Self::Lowered(rest) => rest.next(),
}
}
}
impl<I: Iterator<Item = char>, const FOLD_CASE: bool> Mapped<I, FOLD_CASE> {
fn over(inner: I) -> Self {
Self {
inner,
folded: None,
}
}
fn fold(&mut self, c: char) -> Option<char> {
match fold::of(c) {
fold::Fold::Mapped(mapped) => {
let mut rest = mapped.chars();
let first = rest.next();
self.folded = Some(Folded::Mapped(rest));
first
}
fold::Fold::One(one) => {
self.folded = None;
Some(one)
}
fold::Fold::Lowercase => {
let mut lowered = c.to_lowercase();
let first = lowered.next();
self.folded = Some(Folded::Lowered(lowered));
first
}
}
}
}
impl<I: Iterator<Item = char>, const FOLD_CASE: bool> Iterator for Mapped<I, FOLD_CASE> {
type Item = char;
fn next(&mut self) -> Option<char> {
if let Some(rest) = self.folded.as_mut()
&& let Some(c) = rest.next()
{
return Some(c);
}
self.folded = None;
for c in self.inner.by_ref() {
if mapped_to_nothing(c) {
continue;
}
if c.is_whitespace() {
return Some(' ');
}
return if FOLD_CASE { self.fold(c) } else { Some(c) };
}
None
}
}
struct Spaced<I> {
inner: I,
held: Option<char>,
gap: bool,
started: bool,
}
impl<I: Iterator<Item = char>> Spaced<I> {
fn over(inner: I) -> Self {
Self {
inner,
held: None,
gap: false,
started: false,
}
}
}
impl<I: Iterator<Item = char>> Iterator for Spaced<I> {
type Item = char;
fn next(&mut self) -> Option<char> {
if let Some(c) = self.held.take() {
return Some(c);
}
for c in self.inner.by_ref() {
if c == ' ' {
self.gap = self.started;
continue;
}
self.started = true;
if self.gap {
self.gap = false;
self.held = Some(c);
return Some(' ');
}
return Some(c);
}
None
}
}
impl<'a> Attribute<'a> {
#[must_use]
pub fn kind(self) -> Oid<'a> {
self.kind
}
#[must_use]
pub fn value(self) -> AttributeValue<'a> {
self.value
}
pub fn text(self, execution: &Memory) -> Result<Option<OwnedString>> {
self.value.text(execution)
}
#[cfg(test)]
fn matches(self, other: &Attribute<'_>) -> bool {
if self.kind != other.kind {
return false;
}
if self.value.der == other.value.der {
return true;
}
match (self.folding_chars(), other.folding_chars()) {
(Some(left), Some(right)) => canonical(left).eq(canonical(right)),
_ => false,
}
}
#[cfg(test)]
fn order(&self, other: &Attribute<'_>) -> core::cmp::Ordering {
self.kind
.as_bytes()
.cmp(other.kind.as_bytes())
.then_with(|| match (self.folding_chars(), other.folding_chars()) {
(Some(left), Some(right)) => canonical(left).cmp(canonical(right)),
(None, None) => self.value.der.cmp(other.value.der),
(Some(_), None) => core::cmp::Ordering::Less,
(None, Some(_)) => core::cmp::Ordering::Greater,
})
}
pub fn folds(self, execution: &Memory) -> Result<bool> {
prepared::folds(self, execution)
}
pub fn folds_in(self, parent: &Reader<'_, '_>) -> Result<bool> {
prepared::folds_in(self, parent)
}
#[cfg(test)]
fn folding_chars(self) -> Option<DecodedChars<'a>> {
(permits(self.kind, self.value.encoding()) && preparable(&self))
.then(|| self.value.decoded_chars())
}
}
impl<'a> AttributeValue<'a> {
#[must_use]
pub fn as_bytes(self) -> &'a [u8] {
self.der
}
#[must_use]
pub fn contents(self) -> &'a [u8] {
self.contents
}
#[must_use]
pub fn encoding(self) -> Encoding {
if self.tag.class() != Class::Universal || self.tag.is_constructed() {
return Encoding::Other;
}
match self.tag.number() {
Some(universal::UTF8_STRING) => Encoding::Utf8,
Some(universal::PRINTABLE_STRING) => Encoding::Printable,
Some(universal::IA5_STRING) => Encoding::Ia5,
Some(NUMERIC_STRING) => Encoding::Numeric,
Some(TELETEX_STRING) => Encoding::Teletex,
Some(VISIBLE_STRING) => Encoding::Visible,
Some(BMP_STRING) => Encoding::Bmp,
Some(UNIVERSAL_STRING) => Encoding::Universal,
_ => Encoding::Other,
}
}
pub fn text(self, execution: &Memory) -> Result<Option<OwnedString>> {
let fail = |reason| Error::resource(reason, "name.attribute", self.offset);
let width = match self.encoding() {
Encoding::Other => return Ok(None),
Encoding::Bmp => Some(2),
Encoding::Universal => Some(4),
_ => None,
};
execution
.charge_work((self.contents.len() as u64) * 2)
.map_err(fail)?;
match width {
Some(width) => {
if !self.contents.len().is_multiple_of(width) {
return Ok(None);
}
let bound = ((DISPLAY_CEILING + 1) * width).min(self.contents.len());
display_text(wide_chars(&self.contents[..bound], width), execution).map_err(fail)
}
None => match core::str::from_utf8(self.contents) {
Ok(text) => display_text(text.chars().map(Some), execution).map_err(fail),
Err(_) => display_text(
self.contents.iter().map(|byte| Some(char::from(*byte))),
execution,
)
.map_err(fail),
},
}
}
fn validated_count(self, mut keep: impl FnMut(char) -> bool) -> Option<usize> {
let encoding = self.encoding();
let mut count = 0usize;
match encoding {
Encoding::Other => return None,
Encoding::Bmp | Encoding::Universal => {
let width = if encoding == Encoding::Bmp { 2 } else { 4 };
if !self.contents.len().is_multiple_of(width) {
return None;
}
for c in wide_chars(self.contents, width) {
if !keep(c?) {
return None;
}
count += 1;
}
}
_ => {
let text = core::str::from_utf8(self.contents).ok()?;
for c in text.chars() {
if !in_alphabet(encoding, c) || !keep(c) {
return None;
}
count += 1;
}
}
}
Some(count)
}
fn decoded_chars(self) -> DecodedChars<'a> {
match self.encoding() {
Encoding::Bmp => DecodedChars::Wide {
contents: self.contents,
width: 2,
},
Encoding::Universal => DecodedChars::Wide {
contents: self.contents,
width: 4,
},
_ => DecodedChars::Narrow(
core::str::from_utf8(self.contents)
.expect("the value was decoded before this iterator was built")
.chars(),
),
}
}
}
fn in_alphabet(encoding: Encoding, c: char) -> bool {
match encoding {
Encoding::Printable => is_printable_string(c),
Encoding::Numeric => c.is_ascii_digit() || c == ' ',
Encoding::Visible => matches!(c, '\x20'..='\x7e'),
Encoding::Ia5 | Encoding::Teletex => c.is_ascii(),
_ => true,
}
}
fn wide_chars(contents: &[u8], width: usize) -> impl Iterator<Item = Option<char>> + '_ {
contents.chunks_exact(width).map(|chunk| {
char::from_u32(
chunk
.iter()
.fold(0u32, |value, byte| (value << 8) | u32::from(*byte)),
)
})
}
enum DecodedChars<'a> {
Narrow(core::str::Chars<'a>),
Wide { contents: &'a [u8], width: usize },
}
impl Iterator for DecodedChars<'_> {
type Item = char;
fn next(&mut self) -> Option<char> {
match self {
Self::Narrow(chars) => chars.next(),
Self::Wide { contents, width } => {
if contents.len() < *width {
return None;
}
let (unit, rest) = contents.split_at(*width);
*contents = rest;
Some(
char::from_u32(
unit.iter()
.fold(0u32, |value, byte| (value << 8) | u32::from(*byte)),
)
.expect("the value was decoded before this iterator was built"),
)
}
}
}
}
const REAL: u32 = 9;
const ENUMERATED: u32 = 10;
const RELATIVE_OID: u32 = 13;
const NUMERIC_STRING: u32 = 18;
const TELETEX_STRING: u32 = 20;
const VISIBLE_STRING: u32 = 26;
const UNIVERSAL_STRING: u32 = 28;
const BMP_STRING: u32 = 30;
const EXTERNAL: u32 = 8;
const EMBEDDED_PDV: u32 = 11;
const OBJECT_DESCRIPTOR: u32 = 7;
const TIME: u32 = 14;
const DATE: u32 = 31;
const TIME_OF_DAY: u32 = 32;
const DATE_TIME: u32 = 33;
const DURATION: u32 = 34;
const OID_IRI: u32 = 35;
const RELATIVE_OID_IRI: u32 = 36;
fn iri_fits(contents: &[u8], rooted: bool) -> bool {
let Ok(text) = core::str::from_utf8(contents) else {
return false;
};
let arcs = match text.strip_prefix('/') {
Some(rest) if rooted => rest,
None if !rooted => text,
_ => return false,
};
!arcs.is_empty() && arcs.split('/').all(arc_fits)
}
fn arc_fits(arc: &str) -> bool {
if arc.is_empty() {
return false;
}
if arc.bytes().all(|byte| byte.is_ascii_digit()) {
return arc == "0" || !arc.starts_with('0');
}
if arc.starts_with('-') || arc.ends_with('-') {
return false;
}
let hyphenated = arc
.chars()
.nth(2)
.zip(arc.chars().nth(3))
.is_some_and(|(third, fourth)| third == '-' && fourth == '-');
!hyphenated && arc.chars().all(label_character)
}
fn label_character(c: char) -> bool {
matches!(c, '-' | '.' | '_' | '~' | '0'..='9' | 'A'..='Z' | 'a'..='z')
|| matches!(u32::from(c),
0xa0..=0xdffe
| 0xf900..=0xfdcf
| 0xfdf0..=0xffef
| 0x1_0000..=0x1_fffd
| 0x2_0000..=0x2_fffd
| 0x3_0000..=0x3_fffd
| 0x4_0000..=0x4_fffd
| 0x5_0000..=0x5_fffd
| 0x6_0000..=0x6_fffd
| 0x7_0000..=0x7_fffd
| 0x8_0000..=0x8_fffd
| 0x9_0000..=0x9_fffd
| 0xa_0000..=0xa_fffd
| 0xb_0000..=0xb_fffd
| 0xc_0000..=0xc_fffd
| 0xd_0000..=0xd_fffd
| 0xe_1000..=0xe_fffd)
}
pub(crate) fn constructed_universal_type(number: u32) -> bool {
matches!(
number,
universal::SEQUENCE | universal::SET | 8 | 11 | CHARACTER_STRING
)
}
const CHARACTER_STRING: u32 = 29;
fn associated_sequence_fits<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
number: u32,
) -> Result<bool> {
let mut inside = parent.enter(element).field(field)?;
if number == EXTERNAL {
let mut left: &[u32] = &[universal::OID, universal::INTEGER, OBJECT_DESCRIPTOR];
while let Some(member) = inside.next_element().field(field)? {
let tag = member.tag();
if tag.class() == Class::ContextSpecific {
let carried = match tag.number() {
Some(0) => {
let mut held = inside.enter(&member).field(field)?;
held.next_element().field(field)?.is_some() && held.is_empty()
}
Some(1) => segments::implicit(&inside, &member, field, false)?,
Some(2) => segments::implicit(&inside, &member, field, true)?,
_ => false,
};
return Ok(carried && inside.is_empty());
}
let Some(at) = tag
.number()
.filter(|_| tag.class() == Class::Universal)
.and_then(|number| left.iter().position(|kind| *kind == number))
else {
return Ok(false);
};
left = &left[at + 1..];
}
return Ok(false);
}
let Some(identification) = inside.next_element().field(field)? else {
return Ok(false);
};
if identification.tag() != Tag::context(true, 0) {
return Ok(false);
}
let mut chosen = inside.enter(&identification).field(field)?;
let Some(alternative) = chosen.next_element().field(field)? else {
return Ok(false);
};
if alternative.tag().class() != Class::ContextSpecific
|| !chosen.is_empty()
|| !identification_fits(&chosen, &alternative, field)?
{
return Ok(false);
}
let Some(value) = inside.next_element().field(field)? else {
return Ok(false);
};
Ok(value.tag().class() == Class::ContextSpecific
&& value.tag().number() == Some(2)
&& segments::implicit(&inside, &value, field, false)?
&& inside.is_empty())
}
fn reads<T>(outcome: structured_asn1::Result<T>, field: &'static str) -> Result<bool> {
match outcome.field(field) {
Ok(_) => Ok(true),
Err(error) if error.is_resource() => Err(error),
Err(_) => Ok(false),
}
}
fn identification_fits<'a>(
parent: &Reader<'a, '_>,
alternative: &Element<'a>,
field: &'static str,
) -> Result<bool> {
fn pair<'a>(
parent: &Reader<'a, '_>,
alternative: &Element<'a>,
field: &'static str,
numbered: bool,
) -> Result<bool> {
let mut inside = parent.enter(alternative).field(field)?;
let (Some(first), Some(second)) = (
inside.next_element().field(field)?,
inside.next_element().field(field)?,
) else {
return Ok(false);
};
let opens = if numbered {
reads(inside.integer(&first), field)?
} else {
reads(inside.oid(&first), field)?
};
Ok(opens
&& first.tag() == Tag::context(false, 0)
&& second.tag() == Tag::context(false, 1)
&& reads(inside.oid(&second), field)?
&& inside.is_empty())
}
let constructed = alternative.tag().is_constructed();
Ok(match alternative.tag().number() {
Some(0) => constructed && pair(parent, alternative, field, false)?,
Some(1 | 4) => reads(parent.oid(alternative), field)?,
Some(2) => reads(parent.integer(alternative), field)?,
Some(3) => constructed && pair(parent, alternative, field, true)?,
Some(5) => !constructed && alternative.contents().is_empty(),
_ => false,
})
}
fn assigned_universal_type(number: u32) -> bool {
!matches!(number, 0 | 15) && number <= RELATIVE_OID_IRI
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum Strings {
Checked,
AsWritten,
}
pub(crate) fn walk_der<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
strings: Strings,
) -> Result<()> {
if !check_der(parent, element, field, strings)? {
return Ok(());
}
let mut open = parent.execution().vector::<Open<'a, '_>>();
open.try_push(Open::of(parent.enter(element).field(field)?, element.tag()))
.map_err(|reason| Error::resource(reason, field, element.span().start))?;
while let Some(level) = open.last_mut() {
let Some(child) = level.reader.next_element().field(field)? else {
open.pop();
continue;
};
if level.ordered
&& let Some(previous) = level.previous
{
level
.reader
.charge(previous.len().max(child.as_bytes().len()) as u64)
.field(field)?;
if !der_set_ordered(previous, child.as_bytes()) {
return Err(Error::new(
ErrorCode::MalformedValue,
field,
child.span().start,
));
}
}
level.previous = Some(child.as_bytes());
let inside = &open
.last()
.expect("the reader the child was read from")
.reader;
if check_der(inside, &child, field, strings)? {
let entered = inside.enter(&child).field(field)?;
let tag = child.tag();
open.try_push(Open::of(entered, tag))
.map_err(|reason| Error::resource(reason, field, child.span().start))?;
}
}
Ok(())
}
struct Open<'a: 'operation, 'operation> {
reader: Reader<'a, 'operation>,
ordered: bool,
previous: Option<&'a [u8]>,
}
impl<'a: 'operation, 'operation> Open<'a, 'operation> {
fn of(reader: Reader<'a, 'operation>, tag: Tag) -> Self {
let ordered = reader.encoding().is_der()
&& tag.class() == Class::Universal
&& tag.number() == Some(universal::SET);
Self {
reader,
ordered,
previous: None,
}
}
}
fn check_der<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
strings: Strings,
) -> Result<bool> {
let fail = |code| Error::new(code, field, element.span().start);
let tag = element.tag();
if tag.class() == Class::Universal {
if !tag.number().is_some_and(assigned_universal_type) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let container = tag.number().is_some_and(constructed_universal_type);
let wrong = if tag.is_constructed() {
!container
&& (parent.encoding().is_der()
|| !tag.number().is_some_and(segmented_universal_type))
} else {
container
};
if wrong {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if tag.is_constructed() && tag.number().is_some_and(segmented_universal_type) {
segments::validate(parent, element, field, strings)?;
return Ok(false);
}
if let Some(number @ (EXTERNAL | EMBEDDED_PDV | CHARACTER_STRING)) = tag.number()
&& !associated_sequence_fits(parent, element, field, number)?
{
return Err(fail(ErrorCode::MalformedValue));
}
}
if !tag.is_constructed() {
if tag.class() == Class::Universal {
match tag.number() {
Some(universal::INTEGER) => parent.integer(element).field(field).map(|_| ())?,
Some(universal::BIT_STRING) => {
parent.bit_string(element).field(field).map(|_| ())?;
}
Some(universal::BOOLEAN) => parent.boolean(element).field(field).map(|_| ())?,
Some(universal::NULL) => parent.null(element).field(field)?,
Some(universal::OID) => parent.oid(element).field(field).map(|_| ())?,
Some(ENUMERATED) => parent.integer(element).field(field).map(|_| ())?,
Some(REAL) => parent.real(element).field(field)?,
Some(RELATIVE_OID) => parent.oid(element).field(field).map(|_| ())?,
Some(universal::UTC_TIME | universal::GENERALIZED_TIME) => {
useful_time_fits(parent, element, field)?;
}
Some(kind @ (TIME | DATE | TIME_OF_DAY | DATE_TIME | DURATION)) => {
parent
.charge(element.contents().len() as u64)
.field(field)?;
let defined = match kind {
DATE => crate::time::Defined::Date,
TIME_OF_DAY => crate::time::Defined::TimeOfDay,
DATE_TIME => crate::time::Defined::DateTime,
DURATION => crate::time::Defined::Duration,
_ => crate::time::Defined::Any,
};
if !crate::time::defined_fits(defined, element.contents()) {
return Err(fail(ErrorCode::MalformedValue));
}
}
Some(kind @ (OID_IRI | RELATIVE_OID_IRI)) => {
parent
.charge(element.contents().len() as u64)
.field(field)?;
if !iri_fits(element.contents(), kind == OID_IRI) {
return Err(fail(ErrorCode::MalformedValue));
}
}
_ if strings == Strings::Checked => {
parent
.charge(element.contents().len() as u64)
.field(field)?;
let value = AttributeValue {
tag,
contents: element.contents(),
der: element.as_bytes(),
offset: element.span().start,
depth: parent.depth(),
};
let settled = !matches!(value.encoding(), Encoding::Other | Encoding::Teletex);
if settled && value.validated_count(|_| true).is_none() {
return Err(fail(ErrorCode::MalformedValue));
}
}
_ => (),
}
}
return Ok(false);
}
Ok(true)
}
fn useful_time_fits(
parent: &Reader<'_, '_>,
element: &Element<'_>,
field: &'static str,
) -> Result<()> {
let generalized = element.tag().number() == Some(universal::GENERALIZED_TIME);
let bytes = element.contents();
if parent.encoding().is_der() {
parent.charge(bytes.len() as u64).field(field)?;
if generalized {
crate::temporal::admit_generalized(parent, element, field)?;
}
crate::time::read_as(element, field, crate::time::Fraction::Written)?;
} else {
let work = (bytes.len() as u64).checked_mul(3).ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
field,
element.span().start,
)
})?;
parent.charge(work).field(field)?;
if !crate::time::ber_fits(bytes, generalized) {
return Err(Error::new(
ErrorCode::MalformedTime,
field,
element.span().start,
));
}
}
Ok(())
}
fn segmented_universal_type(number: u32) -> bool {
matches!(number, 3 | 4 | 7 | 12 | 18..=28 | 30)
}
pub(crate) fn is_ia5(bytes: &[u8]) -> bool {
bytes.iter().all(u8::is_ascii)
}
pub(crate) fn is_numeric(bytes: &[u8]) -> bool {
bytes
.iter()
.all(|&byte| byte.is_ascii_digit() || byte == b' ')
}
pub(crate) fn is_printable(bytes: &[u8]) -> bool {
bytes
.iter()
.all(|&byte| byte.is_ascii() && is_printable_string(char::from(byte)))
}
pub(crate) fn is_printable_string(c: char) -> bool {
c.is_ascii_alphanumeric()
|| matches!(
c,
' ' | '\'' | '(' | ')' | '+' | ',' | '-' | '.' | '/' | ':' | '=' | '?'
)
}
pub(crate) fn der_set_ordered(previous: &[u8], next: &[u8]) -> bool {
let length = previous.len().max(next.len());
for position in 0..length {
let left = previous.get(position).copied().unwrap_or(0);
let right = next.get(position).copied().unwrap_or(0);
if left != right {
return left < right;
}
}
true
}
pub(crate) fn wide_char_count(contents: &[u8], width: usize) -> Option<usize> {
if !contents.len().is_multiple_of(width) {
return None;
}
let mut count = 0usize;
for character in wide_chars(contents, width) {
character?;
count += 1;
}
Some(count)
}
fn display_text(
mut characters: impl Iterator<Item = Option<char>>,
execution: &Memory,
) -> core::result::Result<Option<OwnedString>, structured_asn1::memory::MemoryError> {
let mut text = execution.string();
for index in 0..=DISPLAY_CEILING {
execution.charge_work(1)?;
let Some(character) = characters.next() else {
break;
};
let Some(character) = character else {
return Ok(None);
};
if index == DISPLAY_CEILING {
text.try_push('…')?;
break;
}
text.try_push(character)?;
}
Ok(Some(text))
}
pub fn read<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<Name<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut sequence = parent.enter(element).field(field)?;
let mut parts = parent.execution().vector();
while let Some(part) = sequence.next_element().field(field)? {
if part.tag() != Tag::constructed(universal::SET) {
return Err(Error::new(
ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
field,
part.span().start,
));
}
parts
.try_push(read_relative(&sequence, &part, field)?)
.map_err(|reason| Error::resource(reason, field, part.span().start))?;
}
Ok(Name {
der: element.as_bytes(),
parts: parts.into_owned(),
offset: element.span().start,
field,
})
}
pub(crate) fn read_relative<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<RelativeName<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let mut attributes = parent.execution().vector();
let mut previous: Option<&[u8]> = None;
while let Some(pair) = inside.next_element().field(field)? {
if inside.encoding().is_der()
&& let Some(previous) = previous
&& {
inside
.charge(previous.len().max(pair.as_bytes().len()) as u64)
.field(field)?;
!der_set_ordered(previous, pair.as_bytes())
}
{
return Err(Error::new(
ErrorCode::MalformedValue,
field,
pair.span().start,
));
}
previous = Some(pair.as_bytes());
attributes
.try_push(read_attribute(&inside, &pair, field)?)
.map_err(|reason| Error::resource(reason, field, pair.span().start))?;
}
if attributes.is_empty() {
return Err(fail(ErrorCode::EmptySequence));
}
Ok(RelativeName {
attributes: attributes.into_owned(),
})
}
fn read_attribute<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<Attribute<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut pair = parent.enter(element).field(field)?;
let kind = pair.read_oid().field(field)?;
let value = pair
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
walk_der(&pair, &value, field, Strings::AsWritten)?;
if !pair.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
Ok(Attribute {
kind,
value: AttributeValue {
tag: value.tag(),
contents: value.contents(),
der: value.as_bytes(),
offset: value.span().start,
depth: pair.depth(),
},
})
}
#[cfg(test)]
mod tests;