use super::{Attribute, Encoding, Error, Name, OwnedString, Result};
use structured_asn1::memory::Memory;
impl Name<'_> {
pub fn rfc4514(&self, execution: &Memory) -> Result<OwnedString> {
render(self, execution, false)
}
pub fn rfc4514_readable(&self, execution: &Memory) -> Result<OwnedString> {
render(self, execution, true)
}
}
fn render(name: &Name<'_>, execution: &Memory, readable: bool) -> Result<OwnedString> {
let fail = |reason| Error::resource(reason, name.field, name.offset);
let mut output = execution.string();
execution
.charge_work(name.parts.len() as u64)
.map_err(fail)?;
for (part_at, part) in name.parts.iter().rev().enumerate() {
if part_at != 0 {
output.try_push(',').map_err(fail)?;
}
execution
.charge_work(part.attributes.len() as u64)
.map_err(fail)?;
for (at, attribute) in part.attributes.iter().enumerate() {
if at != 0 {
output.try_push('+').map_err(fail)?;
}
execution
.charge_work(
(attribute.kind.as_bytes().len() as u64)
.checked_mul(super::selector::REGISTERED_DESCRIPTORS as u64)
.ok_or_else(|| fail(structured_asn1::memory::MemoryError::WorkExceeded))?,
)
.map_err(fail)?;
let descriptor = super::selector::registered_name(attribute.kind);
if let Some(descriptor) = descriptor {
output.try_push_str(descriptor).map_err(fail)?;
} else {
let oid = attribute.kind.text(execution).map_err(fail)?;
output
.try_push_str(oid.as_str(execution).map_err(fail)?)
.map_err(fail)?;
}
output.try_push('=').map_err(fail)?;
if readable && descriptor.is_some() && text(*attribute, &mut output, execution)? {
continue;
}
output.try_push('#').map_err(fail)?;
execution
.charge_work(attribute.value.der.len() as u64)
.map_err(fail)?;
for &byte in attribute.value.der {
hex(byte, &mut output).map_err(fail)?;
}
}
}
Ok(output)
}
fn text(attribute: Attribute<'_>, output: &mut OwnedString, execution: &Memory) -> Result<bool> {
let value = attribute.value;
let fail = |reason| Error::resource(reason, "name.attribute", value.offset);
let work = (attribute.kind.as_bytes().len() as u64)
.checked_mul(64)
.and_then(|kind| {
(value.contents.len() as u64)
.checked_mul(6)
.and_then(|n| kind.checked_add(n))
})
.ok_or_else(|| fail(structured_asn1::memory::MemoryError::WorkExceeded))?;
execution.charge_work(work).map_err(fail)?;
if !super::permits(attribute.kind, value.encoding())
|| !super::well_formed(&attribute)
|| value.validated_count(|_| true).is_none()
|| value.encoding() == Encoding::Other
{
return Ok(false);
}
let mut characters = value.decoded_chars().peekable();
let mut first = true;
while let Some(character) = characters.next() {
let last = characters.peek().is_none();
if character == '\0' || character.is_control() {
let mut bytes = [0; 4];
for &byte in character.encode_utf8(&mut bytes).as_bytes() {
output.try_push('\\').map_err(fail)?;
hex(byte, output).map_err(fail)?;
}
} else {
if matches!(character, '"' | '+' | ',' | ';' | '<' | '=' | '>' | '\\')
|| (first && matches!(character, ' ' | '#'))
|| (last && character == ' ')
{
output.try_push('\\').map_err(fail)?;
}
output.try_push(character).map_err(fail)?;
}
first = false;
}
Ok(true)
}
fn hex(
byte: u8,
output: &mut OwnedString,
) -> core::result::Result<(), structured_asn1::memory::MemoryError> {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
output.try_push(char::from(HEX[usize::from(byte >> 4)]))?;
output.try_push(char::from(HEX[usize::from(byte & 15)]))
}
#[cfg(test)]
mod tests {
use crate::name::Selector;
use crate::{fixtures, name, oid};
use structured_asn1::Parser;
#[test]
fn additional_registered_descriptors_render_complete_text() {
let memory = fixtures::memory();
let parser = Parser::der(memory.clone());
for (kind, label, tag, value) in [
(oid::SERIAL_NUMBER, "serialNumber", 0x13, "ABC"),
(oid::SURNAME, "surname", 0x0c, "Doe"),
(oid::GIVEN_NAME, "givenName", 0x0c, "Alice"),
(oid::INITIALS, "initials", 0x0c, "AB"),
(
oid::GENERATION_QUALIFIER,
"generationQualifier",
0x0c,
"III",
),
(oid::TITLE, "title", 0x0c, "Engineer"),
(oid::DN_QUALIFIER, "dnQualifier", 0x13, "ABC"),
(
oid::EMAIL_ADDRESS,
"emailAddress",
0x16,
"alice@example.com",
),
(oid::PSEUDONYM, "pseudonym", 0x0c, "Alice"),
] {
let bytes = fixtures::seq(&[fixtures::set(&[fixtures::seq(&[
fixtures::oid(kind),
fixtures::tlv(tag, value.as_bytes()),
])])]);
let mut reader = parser.read(&bytes, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let name = name::read(&reader, &element, "name").unwrap();
let output = name.rfc4514_readable(&memory).unwrap();
let rendered = output.as_str(&memory).unwrap();
assert_eq!(rendered, alloc::format!("{label}={value}"));
assert!(
Selector::parse(rendered, &memory)
.unwrap()
.matches_name(&name, &memory)
.unwrap()
);
}
let bytes = fixtures::seq(&[fixtures::set(&[fixtures::seq(&[
fixtures::oid(oid::ORGANIZATION_IDENTIFIER),
fixtures::utf8("ABC"),
])])]);
let mut reader = parser.read(&bytes, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let name = name::read(&reader, &element, "name").unwrap();
assert_eq!(
name.rfc4514_readable(&memory)
.unwrap()
.as_str(&memory)
.unwrap(),
"2.5.4.97=#0C03414243"
);
}
#[test]
fn reversible_and_readable_names_match_without_changing_original_tags() {
let memory = fixtures::memory();
let parser = Parser::der(memory.clone());
let bytes = fixtures::seq(&[
fixtures::set(&[fixtures::seq(&[
fixtures::oid(oid::COUNTRY),
fixtures::tlv(0x13, b"UA"),
])]),
fixtures::set(&[fixtures::seq(&[
fixtures::oid(oid::COMMON_NAME),
fixtures::utf8("A,+B=\\C"),
])]),
]);
let mut reader = parser.read(&bytes, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let name = name::read(&reader, &element, "name").unwrap();
let exact = name.rfc4514(&memory).unwrap();
assert_eq!(
exact.as_str(&memory).unwrap(),
"CN=#0C07412C2B423D5C43,C=#13025541"
);
let readable = name.rfc4514_readable(&memory).unwrap();
assert_eq!(
readable.as_str(&memory).unwrap(),
"CN=A\\,\\+B\\=\\\\C,C=UA"
);
for text in [&exact, &readable] {
let selected = Selector::parse(text.as_str(&memory).unwrap(), &memory).unwrap();
assert!(selected.matches_name(&name, &memory).unwrap());
}
}
#[test]
fn readable_output_never_uses_the_display_truncation_ceiling() {
let memory = fixtures::memory();
let parser = Parser::der(memory.clone());
let text = "a".repeat(40_000);
let bytes = fixtures::seq(&[fixtures::set(&[fixtures::seq(&[
fixtures::oid(oid::USER_ID),
fixtures::utf8(&text),
])])]);
let mut reader = parser.read(&bytes, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let name = name::read(&reader, &element, "name").unwrap();
let output = name.rfc4514_readable(&memory).unwrap();
let rendered = output.as_str(&memory).unwrap();
assert_eq!(rendered, alloc::format!("UID={text}"));
let exact = name.rfc4514(&memory).unwrap();
let selector = Selector::parse(exact.as_str(&memory).unwrap(), &memory).unwrap();
assert!(selector.matches_name(&name, &memory).unwrap());
}
}