use super::{CheckedPath, CrlAuthorities, CrlAuthority, MemberStatus, PathStatuses, ResponderPath};
use core::hash::{Hash, Hasher};
use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
use structured_pkix::{ocsp::AuthoritySource, temporal::Reading};
pub struct AuthorityNode<'view, 'certificate, 'document> {
pub statuses: &'view PathStatuses<'view, 'certificate, 'document>,
pub responders: &'view [&'view ResponderPath<'view, 'view, 'certificate, 'document>],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthorityOrigin {
Root {
index: usize,
},
Crl {
node: usize,
member: usize,
source: usize,
delta: bool,
},
Responder {
node: usize,
member: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthorityClosureError {
Roots,
DuplicateNode {
index: usize,
},
DuplicateResponder {
node: usize,
},
MissingPath {
origin: AuthorityOrigin,
},
ForeignRoot {
index: usize,
},
MissingResponder {
origin: AuthorityOrigin,
},
Resource(MemoryError),
}
impl From<MemoryError> for AuthorityClosureError {
fn from(value: MemoryError) -> Self {
Self::Resource(value)
}
}
pub struct AuthorityClosure<'view, 'certificate, 'document> {
roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
nodes: OwnedVec<&'view AuthorityNode<'view, 'certificate, 'document>>,
}
impl<'view, 'certificate, 'document> AuthorityClosure<'view, 'certificate, 'document> {
#[must_use]
pub fn roots(&self) -> &'view [&'view PathStatuses<'view, 'certificate, 'document>] {
self.roots
}
#[must_use]
pub fn nodes(&self) -> &[&'view AuthorityNode<'view, 'certificate, 'document>] {
&self.nodes
}
}
#[derive(Clone, Copy)]
pub(super) struct Key<'view, 'document> {
pub(super) path: &'view CheckedPath<'document>,
pub(super) at: Reading,
}
impl PartialEq for Key<'_, '_> {
fn eq(&self, other: &Self) -> bool {
core::ptr::eq(self.path, other.path) && self.at == other.at
}
}
impl Eq for Key<'_, '_> {}
impl Hash for Key<'_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
core::ptr::from_ref(self.path).hash(state);
self.at.start().seconds().hash(state);
self.at.start().nanos().hash(state);
self.at.start().is_leap_second().hash(state);
self.at.has_subnanosecond_remainder().hash(state);
}
}
impl HashWork for Key<'_, '_> {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(6)
}
}
struct State {
index: usize,
queued: bool,
}
struct Pending<'view, 'certificate, 'document, W: Work> {
supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
index: OwnedMap<Key<'view, 'document>, State, W>,
queue: OwnedVec<&'view AuthorityNode<'view, 'certificate, 'document>, W>,
}
impl<'view, 'document, W: Work> Pending<'view, '_, 'document, W> {
fn need(
&mut self,
path: &'view CheckedPath<'document>,
at: Reading,
origin: AuthorityOrigin,
work: &W,
) -> Result<usize, AuthorityClosureError> {
let Some(state) = self.index.get_mut(&Key { path, at }, work)? else {
return Err(AuthorityClosureError::MissingPath { origin });
};
work.charge_work(1)?;
if !state.queued {
self.queue.try_push(&self.supplied[state.index])?;
state.queued = true;
}
Ok(state.index)
}
}
pub fn close_authorities<'view, 'certificate, 'document, W: Work>(
roots: &'view [&'view PathStatuses<'view, 'certificate, 'document>],
supplied: &'view [AuthorityNode<'view, 'certificate, 'document>],
work: &W,
) -> Result<AuthorityClosure<'view, 'certificate, 'document>, AuthorityClosureError> {
work.charge_work(1)?;
if roots.is_empty() {
return Err(AuthorityClosureError::Roots);
}
let mut pending = Pending {
supplied,
index: work.map(),
queue: work.vector(),
};
let mut responders = work.map();
for (index, node) in supplied.iter().enumerate() {
work.charge_work(1)?;
let key = Key {
path: node.statuses.path(),
at: node.statuses.evaluated_at(),
};
if pending
.index
.try_insert(
key,
State {
index,
queued: false,
},
)?
.is_some()
{
return Err(AuthorityClosureError::DuplicateNode { index });
}
for responder in node.responders {
work.charge_work(1)?;
let key = core::ptr::from_ref(responder.target()).addr();
if responders.try_insert(key, *responder)?.is_some() {
return Err(AuthorityClosureError::DuplicateResponder { node: index });
}
}
}
for (index, root) in roots.iter().enumerate() {
work.charge_work(1)?;
let node = pending.need(
root.path(),
root.evaluated_at(),
AuthorityOrigin::Root { index },
work,
)?;
if !core::ptr::eq(supplied[node].statuses, *root) {
return Err(AuthorityClosureError::ForeignRoot { index });
}
}
let mut cursor = 0;
while cursor < pending.queue.len() {
work.charge_work(1)?;
let inputs = pending.queue[cursor].statuses;
let key = Key {
path: inputs.path(),
at: inputs.evaluated_at(),
};
let index = pending
.index
.get(&key, work)?
.expect("queued node has its index")
.index;
follow(
inputs,
index,
&responders,
&mut |path, at, origin, work| pending.need(path, at, origin, work).map(|_| ()),
work,
)?;
cursor += 1;
}
Ok(AuthorityClosure {
roots,
nodes: pending.queue.into_owned(),
})
}
pub(super) fn follow<'view, 'certificate, 'document, W: Work>(
inputs: &'view PathStatuses<'view, 'certificate, 'document>,
index: usize,
responders: &OwnedMap<usize, &'view ResponderPath<'view, 'view, 'certificate, 'document>, W>,
need: &mut impl FnMut(
&'view CheckedPath<'document>,
Reading,
AuthorityOrigin,
&W,
) -> Result<(), AuthorityClosureError>,
work: &W,
) -> Result<(), AuthorityClosureError> {
for (member, input) in inputs.entries().iter().enumerate() {
work.charge_work(1)?;
match input {
MemberStatus::Crl(bound) => {
for (source, authority) in bound.authorities().iter().enumerate() {
work.charge_work(1)?;
follow_crl(
(index, member, source),
*authority,
inputs.evaluated_at(),
need,
work,
)?;
}
}
MemberStatus::CrlReading(bound) => follow_crl(
(index, member, 0),
bound.authorities(),
inputs.evaluated_at(),
need,
work,
)?,
MemberStatus::Ocsp(target) => {
if matches!(target.authority(), AuthoritySource::Delegate { .. }) {
let origin = AuthorityOrigin::Responder {
node: index,
member,
};
let key = core::ptr::from_ref(*target).addr();
let responder = responders
.get(&key, work)?
.ok_or(AuthorityClosureError::MissingResponder { origin })?;
need(responder.path(), inputs.evaluated_at(), origin, work)?;
}
}
MemberStatus::ResponderNoCheck(_) => (),
}
}
Ok(())
}
fn follow_crl<'view, 'document, W: Work>(
position: (usize, usize, usize),
authorities: CrlAuthorities<'view, 'document>,
at: Reading,
visit: &mut impl FnMut(
&'view CheckedPath<'document>,
Reading,
AuthorityOrigin,
&W,
) -> Result<(), AuthorityClosureError>,
work: &W,
) -> Result<(), AuthorityClosureError> {
let (node, member, source) = position;
let mut need = |authority, delta| {
if let CrlAuthority::Path(path) = authority {
visit(
path,
at,
AuthorityOrigin::Crl {
node,
member,
source,
delta,
},
work,
)?;
}
Ok::<_, AuthorityClosureError>(())
};
match authorities {
CrlAuthorities::Complete(authority) => need(authority, false),
CrlAuthorities::Delta { base, delta } => {
need(base, false)?;
need(delta, true)
}
}
}