use super::{Meter, cost};
use crate::constraints::NameConstraints;
use crate::error::{CallbackError, CallbackResult, Result};
use crate::extension::Recognizer;
use crate::general_name::GeneralName;
use crate::name::Name;
use crate::oid;
use structured_asn1::Oid;
use structured_asn1::memory::{Memory, MemoryError, OwnedVec};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Fit {
Inside,
Outside,
Elsewhere,
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Admission {
Admitted,
Excluded,
NotPermitted,
Unenforceable,
}
#[derive(Debug, Clone, Copy)]
enum Presented<'a> {
Directory(&'a Name<'a>),
Email(&'a [u8]),
Dns(&'a [u8]),
Uri(&'a [u8]),
Host(&'a [u8]),
Ip(&'a [u8]),
Opaque(Form<'a>),
Unreadable(Malformed),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Malformed {
Mailbox,
Location,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Form<'a> {
Other(Oid<'a>),
X400,
EdiParty,
RegisteredId,
}
const SHAPES: usize = 6;
impl Presented<'_> {
fn shape(self) -> usize {
match self {
Self::Directory(_) => 0,
Self::Email(_) | Self::Unreadable(Malformed::Mailbox) => 1,
Self::Dns(_) => 2,
Self::Uri(_) | Self::Host(_) | Self::Unreadable(Malformed::Location) => 3,
Self::Ip(_) => 4,
Self::Opaque(_) => 5,
}
}
fn weight(self) -> u64 {
match self {
Self::Directory(name) => name.as_bytes().len() as u64,
Self::Email(text) | Self::Dns(text) | Self::Uri(text) | Self::Host(text) => {
text.len() as u64
}
Self::Ip(octets) => octets.len() as u64,
Self::Opaque(Form::Other(identifier)) => identifier.as_bytes().len() as u64,
Self::Opaque(_) | Self::Unreadable(_) => 0,
}
}
}
impl<'a> Presented<'a> {
fn of(name: &'a GeneralName<'a>) -> Self {
match name {
GeneralName::Directory(name) => Self::Directory(name),
GeneralName::Email(address) => Self::Email(address.as_bytes()),
GeneralName::Dns(host) => Self::Dns(host.as_bytes()),
GeneralName::Uri(location) => Self::Uri(location.as_bytes()),
GeneralName::IpAddress(address) => Self::Ip(address),
GeneralName::Other { type_id, .. } => Self::Opaque(Form::Other(*type_id)),
GeneralName::X400(_) => Self::Opaque(Form::X400),
GeneralName::EdiParty(_) => Self::Opaque(Form::EdiParty),
GeneralName::RegisteredId(_) => Self::Opaque(Form::RegisteredId),
}
}
}
#[derive(Debug)]
pub(super) struct NameSpace<'a> {
layers: OwnedVec<NameConstraints<'a>>,
}
impl<'a> NameSpace<'a> {
pub(super) fn new(execution: &Memory) -> Self {
Self {
layers: execution.vector(),
}
}
pub(super) fn narrow(
&mut self,
constraints: NameConstraints<'a>,
) -> core::result::Result<(), MemoryError> {
self.layers.try_push(constraints)
}
pub(super) fn is_open(&self) -> bool {
self.layers.is_empty()
}
#[cfg(test)]
pub(super) fn regions(&self) -> usize {
self.layers
.iter()
.map(|layer| layer.permitted().len() + layer.excluded().len())
.sum()
}
pub(super) fn charge(
&self,
subject: &Name<'_>,
alternatives: &[GeneralName<'_>],
meter: &mut impl Meter,
execution: &Memory,
) -> Result<bool> {
let mut regions = [0u64; SHAPES];
for layer in &self.layers {
for region in layer.permitted().iter().chain(layer.excluded().iter()) {
if !meter.spend(cost::ENTRY)? {
return Ok(false);
}
regions[Presented::of(region.base()).shape()] += 1;
}
}
for unformed in unformed(subject, alternatives, execution) {
let unformed = unformed?;
if !meter.spend(unformed.forming() / cost::COMPARED)? {
return Ok(false);
}
execution
.charge_work(1 + unformed.forming())
.map_err(|reason| subject.resource(reason))?;
let name = unformed.form();
let against = regions[name.shape()];
let reads = against
.checked_mul(name.weight())
.ok_or_else(|| subject.resource(MemoryError::WorkExceeded))?
/ cost::COMPARED;
let work = cost::NAME
.checked_mul(against)
.and_then(|work| work.checked_add(reads))
.ok_or_else(|| subject.resource(MemoryError::WorkExceeded))?;
if !meter.spend(work)? {
return Ok(false);
}
}
Ok(true)
}
pub(super) fn admits<E>(
&self,
subject: &Name<'_>,
alternatives: &[GeneralName<'_>],
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<Admission, E> {
if self.layers.is_empty() {
return Ok(Admission::Admitted);
}
let mut worst = Admission::Admitted;
for name in presented(subject, alternatives, execution) {
match self.admits_one(name?, subject, recognizer, execution)? {
Admission::Excluded => return Ok(Admission::Excluded),
Admission::NotPermitted => worst = Admission::NotPermitted,
Admission::Unenforceable if worst == Admission::Admitted => {
worst = Admission::Unenforceable;
}
_ => {}
}
}
Ok(worst)
}
fn admits_one<E>(
&self,
name: Presented<'_>,
origin: &Name<'_>,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<Admission, E> {
let mut worst = Admission::Admitted;
for layer in &self.layers {
let mut unreadable = false;
for region in layer.excluded() {
execution
.charge_work(1)
.map_err(|reason| origin.resource(reason))?;
match fit(region.base(), name, origin, recognizer, execution)? {
Fit::Inside => return Ok(Admission::Excluded),
Fit::Unknown => unreadable = true,
Fit::Outside | Fit::Elsewhere => {}
}
}
let mut spoken_for = false;
let mut inside = false;
for region in layer.permitted() {
execution
.charge_work(1)
.map_err(|reason| origin.resource(reason))?;
match fit(region.base(), name, origin, recognizer, execution)? {
Fit::Inside => {
inside = true;
break;
}
Fit::Outside => spoken_for = true,
Fit::Unknown => {
spoken_for = true;
unreadable = true;
}
Fit::Elsewhere => {}
}
}
if !inside && spoken_for {
return Ok(if unreadable {
Admission::Unenforceable
} else {
Admission::NotPermitted
});
}
if unreadable && worst == Admission::Admitted {
worst = Admission::Unenforceable;
}
}
Ok(worst)
}
}
fn presented_mailbox(value: &[u8]) -> Presented<'_> {
match core::str::from_utf8(value) {
Ok(text) if crate::general_name::is_mailbox(text) => Presented::Email(value),
_ => Presented::Unreadable(Malformed::Mailbox),
}
}
#[derive(Clone, Copy)]
enum Unformed<'a> {
Directory(&'a Name<'a>),
Mailbox(&'a [u8]),
Alternative(&'a GeneralName<'a>),
}
impl<'a> Unformed<'a> {
fn forming(self) -> u64 {
match self {
Self::Mailbox(value) => value.len() as u64,
Self::Alternative(GeneralName::Uri(location)) => location.len() as u64,
Self::Directory(_) | Self::Alternative(_) => 0,
}
}
fn form(self) -> Presented<'a> {
match self {
Self::Directory(subject) => Presented::Directory(subject),
Self::Mailbox(value) => presented_mailbox(value),
Self::Alternative(name) => presented_name(name),
}
}
}
fn unformed<'a, 'q>(
subject: &'a Name<'a>,
alternatives: &'a [GeneralName<'a>],
execution: &'q Memory,
) -> impl Iterator<Item = Result<Unformed<'a>>> + 'q
where
'a: 'q,
{
let directory = (!subject.is_empty()).then_some(Unformed::Directory(subject));
let mailboxes = alternatives
.is_empty()
.then(|| {
subject
.attributes_of(oid::EMAIL_ADDRESS, execution)
.map(|attribute| {
attribute.map(|attribute| Unformed::Mailbox(attribute.value().contents()))
})
})
.into_iter()
.flatten();
directory.into_iter().map(Ok).chain(mailboxes).chain(
alternatives
.iter()
.map(|name| Ok(Unformed::Alternative(name))),
)
}
fn presented<'a, 'q>(
subject: &'a Name<'a>,
alternatives: &'a [GeneralName<'a>],
execution: &'q Memory,
) -> impl Iterator<Item = Result<Presented<'a>>> + 'q
where
'a: 'q,
{
unformed(subject, alternatives, execution).map(move |name| {
let name = name?;
execution
.charge_work(1 + name.forming())
.map_err(|reason| subject.resource(reason))?;
Ok(name.form())
})
}
fn presented_name<'a>(name: &'a GeneralName<'a>) -> Presented<'a> {
match name {
GeneralName::Uri(location) => match host_of(location.as_bytes()) {
Some(host) => Presented::Host(host),
None => Presented::Unreadable(Malformed::Location),
},
other => Presented::of(other),
}
}
fn fit<E>(
base: &GeneralName<'_>,
name: Presented<'_>,
origin: &Name<'_>,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<Fit, E> {
let admit = |work| {
execution
.charge_work(work)
.map_err(|reason| origin.resource(reason))
};
Ok(match (Presented::of(base), name) {
(Presented::Directory(region), Presented::Directory(name)) => {
decide(name.starts_with(region, execution)?)
}
(Presented::Dns(region), Presented::Dns(name)) => {
admit(region.len().min(name.len()) as u64)?;
decide(host_within(region, name))
}
(Presented::Email(region), Presented::Email(name)) => {
let work = (region.len() as u64)
.checked_add(name.len() as u64)
.and_then(|work| work.checked_mul(2))
.ok_or_else(|| origin.resource(MemoryError::WorkExceeded))?;
admit(work)?;
decide(mailbox_within(region, name))
}
(Presented::Uri(region), Presented::Host(host)) => {
admit(region.len().min(host.len()) as u64)?;
decide(at_or_under(region, host))
}
(Presented::Ip(region), Presented::Ip(name)) => {
admit(name.len() as u64)?;
decide(network_within(region, name))
}
(Presented::Email(_), Presented::Unreadable(Malformed::Mailbox))
| (Presented::Uri(_), Presented::Unreadable(Malformed::Location)) => Fit::Unknown,
(Presented::Opaque(Form::Other(region)), Presented::Opaque(Form::Other(name))) => {
if region.as_bytes().len() != name.as_bytes().len() {
return Ok(Fit::Elsewhere);
}
admit(region.as_bytes().len() as u64)?;
if region != name {
return Ok(Fit::Elsewhere);
}
if recognizer
.recognizes_name_constraint(region, execution)
.map_err(CallbackError::Callback)?
{
Fit::Elsewhere
} else {
Fit::Unknown
}
}
(Presented::Opaque(region), Presented::Opaque(name)) if region == name => Fit::Unknown,
_ => Fit::Elsewhere,
})
}
fn decide(inside: bool) -> Fit {
if inside { Fit::Inside } else { Fit::Outside }
}
fn host_within(region: &[u8], name: &[u8]) -> bool {
if region.is_empty() {
return true;
}
let Some(rest) = name.len().checked_sub(region.len()) else {
return false;
};
if !name[rest..].eq_ignore_ascii_case(region) {
return false;
}
rest == 0 || region.first() == Some(&b'.') || name[rest - 1] == b'.'
}
fn mailbox_within(region: &[u8], name: &[u8]) -> bool {
let Some(host) = host_of_mailbox(name) else {
return false;
};
match host_of_mailbox(region) {
Some(region_host) => {
let local = name.len() - host.len();
let region_local = region.len() - region_host.len();
region[..region_local] == name[..local] && region_host.eq_ignore_ascii_case(host)
}
None => at_or_under(region, host),
}
}
fn at_or_under(region: &[u8], host: &[u8]) -> bool {
if region.first() == Some(&b'.') {
return host.len() > region.len() && host_within(region, host);
}
region.eq_ignore_ascii_case(host)
}
fn host_of_mailbox(mailbox: &[u8]) -> Option<&[u8]> {
let at = mailbox.iter().rposition(|byte| *byte == b'@')?;
Some(&mailbox[at + 1..])
}
fn host_of(location: &[u8]) -> Option<&[u8]> {
let scheme = location.iter().position(|byte| *byte == b':')?;
let rest = location.get(scheme + 1..)?.strip_prefix(b"//".as_slice())?;
let end = rest
.iter()
.position(|byte| matches!(byte, b'/' | b'?' | b'#'))
.unwrap_or(rest.len());
let authority = &rest[..end];
let authority = match authority.iter().rposition(|byte| *byte == b'@') {
Some(at) => &authority[at + 1..],
None => authority,
};
if authority.first() == Some(&b'[') {
return None;
}
let host = match authority.iter().rposition(|byte| *byte == b':') {
Some(at) => &authority[..at],
None => authority,
};
let host = host.strip_suffix(b".".as_slice()).unwrap_or(host);
core::str::from_utf8(host)
.ok()
.filter(|host| crate::general_name::is_domain(host))
.map(str::as_bytes)
}
fn network_within(region: &[u8], name: &[u8]) -> bool {
if region.len() != name.len() * 2 {
return false;
}
let (network, mask) = region.split_at(name.len());
network
.iter()
.zip(mask)
.zip(name)
.all(|((network, mask), address)| network & mask == address & mask)
}
#[cfg(test)]
mod tests;