use super::{Check, Reason, charge, refused};
use crate::attribute_certificate::controls::{self, AaControls};
use crate::{Error, Result};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Oid, Span};
use structured_pkix::Certificate;
pub(super) mod validated;
pub use validated::{
Issued, PathCheck, PathError, PathResult, ValidatedPath, validate, validate_with_key,
};
#[derive(Debug)]
struct Control<'a> {
position: usize,
value: AaControls<'a>,
}
#[derive(Debug)]
pub struct AaPath<'p, 'a> {
certificates: &'p [&'p Certificate<'a>],
controls: OwnedVec<Control<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Permission {
Permitted,
Excluded {
certificate: usize,
source: Span,
},
}
impl<'p, 'a> AaPath<'p, 'a> {
pub fn read<W: Work>(certificates: &'p [&'p Certificate<'a>], work: &W) -> Result<Check<Self>> {
let Some(last) = certificates.len().checked_sub(1) else {
return Ok(Check::Rejected(super::Rejection {
reason: Reason::EmptyPath,
source: None,
}));
};
charge(work, 1, certificates[0].span().start)?;
let mut controls = work.vector().into_owned();
let mut distance = 0;
for (position, certificate) in certificates.iter().enumerate() {
let execution = certificate.execution_in(work);
charge(&execution, 1, certificate.span().start)?;
let extension = certificate
.tbs()
.extensions()
.get(controls::IDENTIFIER, &execution)
.map_err(Error::pkix)?;
if let Some(extension) = extension {
let Ok(mut scoped) = controls.with_execution(&execution) else {
unreachable!("control storage retains this operation's account");
};
scoped
.try_reserve(1)
.map_err(|e| Error::resource(e, super::FIELD, extension.span().start))?;
let value = AaControls::read_extension(extension, &execution)?;
if !value.permits_distance(distance, &execution)? {
return Ok(refused(Reason::ControlDistance, value.element.span()));
}
scoped
.try_push(Control { position, value })
.map_err(|e| Error::resource(e, super::FIELD, extension.span().start))?;
controls = scoped.into_owned();
} else if position != last {
return Ok(refused(Reason::MissingControls, certificate.span()));
}
if position != 0
&& position != last
&& !certificate
.tbs()
.subject()
.matches(certificate.tbs().issuer(), &execution)
.map_err(Error::pkix)?
{
distance += 1;
}
}
Ok(Check::Accepted(Self {
certificates,
controls: controls.into_owned(),
}))
}
#[must_use]
pub fn certificates(&self) -> &[&Certificate<'a>] {
self.certificates
}
pub fn allows<W: Work>(&self, attribute: Oid<'_>, work: &W) -> Result<Permission> {
charge(work, 1, self.certificates[0].span().start)?;
for control in &self.controls {
charge(work, 1, control.value.element.span().start)?;
if !control.value.allows(attribute, work)? {
return Ok(Permission::Excluded {
certificate: control.position,
source: control.value.element.span(),
});
}
}
Ok(Permission::Permitted)
}
}