use super::counters::collect_counters;
use super::{Cause, Entry, RetainError, Source, Value};
use crate::binary::{Located, Location, OwnedBinaryXml};
use crate::prepare::Prepared;
use crate::unsigned;
use crate::xml::{Access, AccessError, Xml};
use structured_asn1::{
Reader,
memory::{HashWork, MemoryError, OwnedVec, Work},
};
use structured_pkix::ocsp::{BasicResponse, Response};
#[derive(Debug, Clone, Copy)]
pub enum Placement<N> {
KeyInfo(N),
Signature(N),
Attribute(N),
General(N),
Timestamp(N),
TimestampToken {
property: N,
token: N,
role: crate::timestamp::Role,
},
}
pub struct Retained<N, D, G> {
placement: Placement<N>,
source: Source<N, D, G>,
}
impl<N: Copy, D: Copy, G: Copy> Retained<N, D, G> {
#[must_use]
pub fn placement(&self) -> Placement<N> {
self.placement
}
#[must_use]
pub fn source(&self) -> &Source<N, D, G> {
&self.source
}
}
#[derive(Debug, Clone, Copy)]
pub struct Opaque<N, D, G> {
pub placement: Placement<N>,
pub location: Location<N, D, G>,
pub entry: Entry<N>,
}
pub struct Basic<'source, N, D, G> {
source_index: usize,
location: Location<N, D, G>,
response: BasicResponse<'source>,
}
impl<'source, N: Copy, D: Copy, G: Copy> Basic<'source, N, D, G> {
#[must_use]
pub fn source_index(&self) -> usize {
self.source_index
}
#[must_use]
pub fn location(&self) -> Location<N, D, G> {
self.location
}
#[must_use]
pub fn response(&self) -> &BasicResponse<'source> {
&self.response
}
}
pub struct View<'source, N, D, G> {
pub(super) snapshot: crate::xml::Snapshot<D, G>,
pub(super) signature: N,
pub(super) sources: &'source [Retained<N, D, G>],
pub(super) values: &'source [Located<Value<'source>, N, D, G>],
opaque: &'source [Opaque<N, D, G>],
pub(super) basic: &'source [Basic<'source, N, D, G>],
}
impl<'source, N, D, G> View<'source, N, D, G> {
#[must_use]
pub fn sources(&self) -> &'source [Retained<N, D, G>] {
self.sources
}
#[must_use]
pub fn values(&self) -> &'source [Located<Value<'source>, N, D, G>] {
self.values
}
#[must_use]
pub fn opaque(&self) -> &'source [Opaque<N, D, G>] {
self.opaque
}
#[must_use]
pub fn basic_responses(&self) -> &'source [Basic<'source, N, D, G>] {
self.basic
}
}
impl<N: Copy, D: Copy, G: Copy> View<'_, N, D, G> {
#[must_use]
pub fn snapshot(&self) -> crate::xml::Snapshot<D, G> {
self.snapshot
}
#[must_use]
pub fn signature(&self) -> N {
self.signature
}
}
#[derive(Debug)]
pub enum Error<E, VE, N, D, G> {
Access(AccessError<E, N>),
Property(crate::properties::Error<E, N>),
Validation(crate::validation::Error<E, N>),
Retain {
location: Location<N, D, G>,
cause: RetainError<E, N>,
},
Parse(Located<Cause, N, D, G>),
Foreign {
node: N,
},
Resource {
node: N,
cause: MemoryError,
},
Visitor(VE),
}
pub type PipelineResult<T, X, VE> = Result<
T,
Error<<X as Xml>::Error, VE, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>,
>;
pub(super) type Record<X> = Retained<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
pub(super) type Open<X> = Opaque<<X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>;
type Basics<'source, X, W> =
OwnedVec<Basic<'source, <X as Xml>::Id, <X as Xml>::Document, <X as Xml>::Generation>, W>;
pub fn with_values<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
operation: &Reader<'_>,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
View<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> PipelineResult<R, X, VE> {
with_inputs(xml, prepared, None, operation, visitor)
}
pub fn with_counter_values<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
counters: &CounterSources<'_, '_, X>,
operation: &Reader<'_>,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
View<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> PipelineResult<R, X, VE> {
with_inputs(xml, prepared, Some(counters), operation, visitor)
}
pub(super) type CounterSources<'view, 'document, X> = crate::pki::CounterPaths<
'view,
'document,
<X as Xml>::Id,
<X as Xml>::Document,
<X as Xml>::Generation,
>;
fn with_inputs<X: OwnedBinaryXml, R, VE>(
xml: &X,
prepared: &Prepared<X>,
counters: Option<&CounterSources<'_, '_, X>>,
operation: &Reader<'_>,
visitor: impl for<'source> FnOnce(
&Reader<'source>,
View<'source, X::Id, X::Document, X::Generation>,
) -> Result<R, VE>,
) -> PipelineResult<R, X, VE> {
let layout = prepared.bound().containers();
let execution = operation.execution();
let access = Access::new(xml, layout.snapshot(), &execution);
admit(&access, layout.signature())?;
if let Some(counters) = counters
&& (counters.snapshot() != layout.snapshot()
|| !access
.equal_id(counters.signature(), layout.signature())
.map_err(|cause| Error::Resource {
node: layout.signature(),
cause,
})?)
{
return Err(Error::Foreign {
node: counters.signature(),
});
}
let mut sources = execution.vector();
let mut opaque = execution.vector();
collect_header(xml, &access, prepared.header(), operation, &mut sources)?;
let parents = material_parents(&access, prepared)?;
for index in 0..prepared.material().len() {
admit(&access, layout.signature())?;
let catalogue = &prepared.material()[index];
let node = catalogue.parent();
let placement = source_placement(parents, node, &execution)
.map_err(|cause| Error::Resource { node, cause })?
.ok_or(Error::Foreign { node })?;
collect(
xml,
&access,
catalogue,
placement,
operation,
&mut sources,
&mut opaque,
)?;
}
for index in 0..prepared.validation().len() {
admit(&access, layout.signature())?;
let data = &prepared.validation()[index];
if data.snapshot() != layout.snapshot()
|| !access
.equal_id(data.signature(), layout.signature())
.map_err(|cause| Error::Resource {
node: data.node(),
cause,
})?
{
return Err(Error::Foreign { node: data.node() });
}
collect_validation(xml, &access, data, operation, &mut sources, &mut opaque)?;
}
if let Some(counters) = counters {
collect_counters(xml, &access, counters, operation, &mut sources, &mut opaque)?;
}
let mut values = execution.vector();
for index in 0..sources.len() {
admit(&access, layout.signature())?;
let source = &sources[index].source;
let value = source.read_in(operation).map_err(Error::Parse)?;
values.try_push(value).map_err(|cause| Error::Resource {
node: source.location().node,
cause,
})?;
}
let basic = read_basics(&access, layout.signature(), &values, operation, &execution)?;
admit(&access, layout.signature())?;
let result = visitor(
operation,
View {
snapshot: layout.snapshot(),
signature: layout.signature(),
sources: &sources,
values: &values,
opaque: &opaque,
basic: &basic,
},
)
.map_err(Error::Visitor)?;
admit(&access, layout.signature())?;
Ok(result)
}
fn read_basics<'source, X: Xml, W: Work, VE>(
access: &Access<'_, X, W>,
signature: X::Id,
values: &[crate::binary::LocatedValue<Value<'source>, X>],
operation: &Reader<'source>,
execution: &W,
) -> PipelineResult<Basics<'source, X, W>, X, VE> {
let mut basic = execution.vector();
let mut cursor = 0;
while cursor < values.len() {
admit(access, signature)?;
let index = cursor;
cursor += 1;
let value = &values[index];
let Value::Ocsp(Response::Successful(envelope)) = &value.value else {
continue;
};
if let Some(response) = envelope.basic_in(operation).map_err(|cause| {
Error::Parse(Located {
location: value.location,
value: Cause::Pki(cause),
})
})? {
basic
.try_push(Basic {
source_index: index,
location: value.location,
response,
})
.map_err(|cause| Error::Resource {
node: value.location.node,
cause,
})?;
}
}
Ok(basic)
}
fn collect_header<X: OwnedBinaryXml, W: Work, VE>(
xml: &X,
access: &Access<'_, X, W>,
header: &crate::header::Reading<X>,
operation: &Reader<'_>,
sources: &mut OwnedVec<Record<X>, W>,
) -> PipelineResult<(), X, VE> {
collect_data(
xml,
access,
header.signature(),
header.x509_data(),
operation,
sources,
)
}
pub(super) fn collect_data<X: OwnedBinaryXml, W: Work, VE>(
xml: &X,
access: &Access<'_, X, W>,
signature: X::Id,
data: X::Id,
operation: &Reader<'_>,
sources: &mut OwnedVec<Record<X>, W>,
) -> PipelineResult<(), X, VE> {
crate::header::visit_values(access, data, |node, kind| {
admit(access, signature)?;
let location = Location {
snapshot: access.snapshot(),
node,
};
let schema = match kind {
crate::header::ValueKind::Certificate => super::Kind::Certificate,
crate::header::ValueKind::Crl => super::Kind::Crl,
};
let source = super::retain_expected(
xml,
location,
schema,
crate::container::DS,
kind.local(),
operation,
)
.map_err(|cause| Error::Retain { location, cause })?;
sources
.try_push(Retained {
placement: Placement::KeyInfo(signature),
source,
})
.map_err(|cause| Error::Resource { node, cause })?;
Ok(())
})
.map_err(|cause| match cause {
crate::reference::VisitError::Processing(cause) => Error::Property(cause),
crate::reference::VisitError::Visitor(cause) => cause,
})
}
fn material_parents<X: Xml, W: Work, VE>(
access: &Access<'_, X, W>,
prepared: &Prepared<X>,
) -> PipelineResult<[Option<X::Id>; 4], X, VE> {
let mut parents = [None; 4];
for index in 0..prepared.unsigned().entries().len() {
admit(access, prepared.unsigned().signature())?;
let entry = prepared.unsigned().entries()[index];
let slot = match entry.role {
unsigned::Role::Certificates => 0,
unsigned::Role::Revocation => 1,
unsigned::Role::AttributeCertificates => 2,
unsigned::Role::AttributeRevocation => 3,
_ => continue,
};
parents[slot] = Some(entry.node);
}
Ok(parents)
}
pub(super) fn collect<X: OwnedBinaryXml, W: Work, VE>(
xml: &X,
access: &Access<'_, X, W>,
catalogue: &super::Catalogue<X::Id, X::Document, X::Generation>,
placement: Placement<X::Id>,
operation: &Reader<'_>,
sources: &mut OwnedVec<Record<X>, W>,
opaque: &mut OwnedVec<Open<X>, W>,
) -> PipelineResult<(), X, VE> {
if catalogue.snapshot() != access.snapshot() {
return Err(Error::Foreign {
node: catalogue.parent(),
});
}
for index in 0..catalogue.entries().len() {
admit(access, catalogue.parent())?;
let entry = catalogue.entries()[index];
match entry {
Entry::Encapsulated { node, kind } => {
let location = Location {
snapshot: catalogue.snapshot(),
node,
};
let source = super::retain(xml, location, kind, operation)
.map_err(|cause| Error::Retain { location, cause })?;
sources
.try_push(Retained { placement, source })
.map_err(|cause| Error::Resource { node, cause })?;
}
Entry::OtherCertificate { node } | Entry::OtherStatus { node } => {
opaque
.try_push(Opaque {
placement,
location: Location {
snapshot: catalogue.snapshot(),
node,
},
entry,
})
.map_err(|cause| Error::Resource { node, cause })?;
}
}
}
Ok(())
}
pub(super) fn collect_validation<X: OwnedBinaryXml, W: Work, VE>(
xml: &X,
access: &Access<'_, X, W>,
data: &crate::validation::Reading<X>,
operation: &Reader<'_>,
sources: &mut OwnedVec<Record<X>, W>,
opaque: &mut OwnedVec<Open<X>, W>,
) -> PipelineResult<(), X, VE> {
if data.snapshot() != access.snapshot() {
return Err(Error::Foreign { node: data.node() });
}
let placement = match data.role() {
crate::validation::Role::Any => Placement::General(data.node()),
crate::validation::Role::Timestamp => Placement::Timestamp(data.node()),
};
for catalogue in [data.certificates(), data.revocations()]
.into_iter()
.flatten()
{
collect(
xml, access, catalogue, placement, operation, sources, opaque,
)?;
}
Ok(())
}
pub(super) fn admit<X: Xml, W: Work, VE>(
access: &Access<'_, X, W>,
node: X::Id,
) -> PipelineResult<(), X, VE> {
access
.query(node, |_, _, work| Ok(work.charge_work(1)))
.map_err(Error::Access)?
.map_err(|cause| Error::Resource { node, cause })
}
fn source_placement<N: Copy + HashWork, W: Work>(
parents: [Option<N>; 4],
node: N,
work: &W,
) -> Result<Option<Placement<N>>, MemoryError> {
for (index, parent) in parents.into_iter().enumerate() {
if let Some(parent) = parent
&& crate::xml::equal_id(parent, node, work)?
{
return Ok(Some(if index < 2 {
Placement::Signature(node)
} else {
Placement::Attribute(node)
}));
}
}
Ok(None)
}
#[cfg(test)]
mod placement_tests {
use super::{Placement, source_placement};
use alloc::sync::Arc;
use structured_asn1::memory::{HashWork, Memory, MemoryAccount, MemoryError};
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
struct Identity(u8, u64);
impl HashWork for Identity {
fn hash_work(&self) -> Result<u64, MemoryError> {
Ok(self.1)
}
}
#[test]
fn placement_pays_the_retained_parent_operand_before_comparison() {
let node = Identity(1, 1);
let parent = Identity(2, 100);
let short = Memory::new(Arc::new(MemoryAccount::new(0, 4)));
assert!(matches!(
source_placement([Some(parent), None, None, None], node, &short),
Err(MemoryError::WorkExceeded)
));
assert_eq!(short.work_remaining(), 4);
let exact = Memory::new(Arc::new(MemoryAccount::new(0, 2)));
assert!(matches!(
source_placement([None, None, Some(node), None], node, &exact),
Ok(Some(Placement::Attribute(_)))
));
assert_eq!(exact.work_remaining(), 0);
}
}