use crate::Error;
use core::cmp::Ordering;
use structured_asn1::memory::{OwnedVec, SortError, Work};
use structured_cms::{
digest::{DigestBatch, DigestError, DigestMemo, PassResult, RequestSource},
stream::{Receiver, Refused, StreamError},
};
use structured_pkix::algorithm::digest::{Algorithm, AsnDigest};
const FIELD: &str = "cades.longTermValidation.input";
#[derive(Debug)]
pub enum InputError<SE, PE> {
Processing(Error),
Digest(DigestError<PE>),
Content(crate::timestamp::InputError<SE>),
Unsupported,
MissingSignedAttributes,
Date(structured_pkix::temporal::CompareError),
Augmentation(crate::timestamp::historical::SelectionError),
Foreign,
Receiver(Refused),
}
pub(super) fn batch<'p, P: AsnDigest<'p>, W: Work, SE>(
provider: &'p P,
algorithms: &[Algorithm],
work: &W,
at: u64,
) -> Result<DigestBatch<'p, P>, InputError<SE, P::Error>> {
let mut identities = work.vector();
identities
.try_reserve_exact(algorithms.len())
.map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
for algorithm in algorithms {
work.charge_work(1)
.map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
identities
.try_push(
provider
.identity(*algorithm)
.ok_or(InputError::Unsupported)?,
)
.map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
}
DigestBatch::prepare(provider, identities.into_owned(), work, FIELD, at)
.map_err(InputError::Digest)
}
pub(super) struct Eight<'p, P: AsnDigest<'p>> {
parts: OwnedVec<DigestMemo<'p, P>>,
covered: OwnedVec<structured_asn1::Span>,
algorithms: OwnedVec<Algorithm>,
at: u64,
}
impl<'p, P: AsnDigest<'p>> Eight<'p, P> {
pub(super) fn build<W: Work, SE>(
provider: &'p P,
algorithms: &[Algorithm],
work: &W,
at: u64,
covered: OwnedVec<structured_asn1::Span>,
mut component: impl FnMut(
usize,
DigestBatch<'p, P>,
&W,
) -> Result<DigestMemo<'p, P>, InputError<SE, P::Error>>,
) -> Result<Self, InputError<SE, P::Error>> {
let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, at));
let mut parts = work.vector();
parts.try_reserve_exact(8).map_err(resource)?;
let mut saved = work.vector();
saved.try_extend_from_slice(algorithms).map_err(resource)?;
for index in 0..8 {
work.charge_work(1).map_err(resource)?;
let pending = batch(provider, algorithms, work, at)?;
let completed = component(index, pending, work)?;
parts.try_push(completed).map_err(resource)?;
}
Ok(Self {
parts: parts.into_owned(),
covered,
algorithms: saved.into_owned(),
at,
})
}
pub(super) fn stream<W: Work, SE>(
&self,
ordinal: usize,
provider: &'p P,
identity: &P::Identity,
out: &mut impl Receiver,
work: &W,
) -> Result<(), InputError<SE, P::Error>> {
work.charge_work(1)
.map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, self.at)))?;
let algorithm = self.algorithms.get(ordinal).ok_or(InputError::Foreign)?;
debug_assert_eq!(self.parts.len(), 8);
let mut outputs = [&[][..]; 8];
for (slot, part) in outputs.iter_mut().zip(&*self.parts) {
*slot = checked(
part,
ordinal,
provider,
identity,
algorithm.width(),
work,
self.at,
)?;
}
for bytes in outputs {
out.write(bytes).map_err(InputError::Receiver)?;
}
Ok(())
}
}
fn checked<'m, 'p, P: AsnDigest<'p>, W: Work, SE>(
memo: &'m DigestMemo<'p, P>,
ordinal: usize,
provider: &'p P,
identity: &P::Identity,
width: usize,
work: &W,
at: u64,
) -> Result<&'m [u8], InputError<SE, P::Error>> {
work.charge_work(1)
.map_err(|cause| InputError::Processing(Error::resource(cause, FIELD, at)))?;
let value = memo
.for_request(ordinal, work)
.map_err(InputError::Digest)?
.ok_or(InputError::Foreign)?;
if !value.finished().from_provider(provider) {
return Err(InputError::Foreign);
}
if provider
.compare(value.identity(), identity, work)
.map_err(|cause| InputError::Digest(DigestError::Callback(cause)))?
!= Ordering::Equal
{
return Err(InputError::Foreign);
}
if value.as_bytes().len() != width {
return Err(InputError::Digest(DigestError::OutputWidth {
expected: width,
actual: value.as_bytes().len(),
}));
}
Ok(value.as_bytes())
}
pub(super) struct Tree<'alg, 'p, P: AsnDigest<'p>, SE> {
provider: &'p P,
items: OwnedVec<DigestMemo<'p, P>>,
algorithms: &'alg [Algorithm],
at: u64,
error: core::marker::PhantomData<fn() -> SE>,
}
impl<'alg, 'p, P: AsnDigest<'p>, SE> Tree<'alg, 'p, P, SE> {
pub(super) fn new(
provider: &'p P,
items: OwnedVec<DigestMemo<'p, P>>,
algorithms: &'alg [Algorithm],
at: u64,
) -> Self {
Self {
provider,
items,
algorithms,
at,
error: core::marker::PhantomData,
}
}
}
impl<'p, P: AsnDigest<'p>, SE> RequestSource<'p, P> for Tree<'_, 'p, P, SE> {
type Error = InputError<SE, P::Error>;
fn stream_request<W: Work>(
&mut self,
ordinal: usize,
provider: &'p P,
identity: &P::Identity,
out: &mut impl Receiver,
work: &W,
) -> Result<(), Self::Error> {
let resource = |cause| InputError::Processing(Error::resource(cause, FIELD, self.at));
work.charge_work(1).map_err(resource)?;
if !core::ptr::eq(provider, self.provider) {
return Err(InputError::Foreign);
}
let width = self
.algorithms
.get(ordinal)
.ok_or(InputError::Foreign)?
.width();
let mut hashes = work.vector();
hashes
.try_reserve_exact(self.items.len())
.map_err(resource)?;
for item in &self.items {
let value = checked(item, ordinal, provider, identity, width, work, self.at)?;
hashes.try_push(value).map_err(resource)?;
}
hashes
.try_sort_unstable_by(|left, right| {
work.charge_work(1 + left.len().min(right.len()) as u64)?;
Ok::<_, structured_asn1::memory::MemoryError>(left.cmp(right))
})
.map_err(|cause| match cause {
SortError::Resource(cause) | SortError::Comparison(cause) => resource(cause),
})?;
for value in &hashes {
work.charge_work(1).map_err(resource)?;
out.write(value).map_err(InputError::Receiver)?;
}
Ok(())
}
}
pub(super) fn derived_result<T, SE, PE>(
result: PassResult<T, InputError<SE, PE>, PE>,
) -> Result<T, InputError<SE, PE>> {
result.map_err(|cause| match cause {
StreamError::Source(cause) => cause,
StreamError::Callback(cause) => InputError::Digest(cause),
StreamError::Processing(cause) => InputError::Processing(Error::Cms(cause)),
})
}
mod check;
mod recipe;
pub use check::{CheckError, CheckResult};
struct Lazy<'s, 'a, 'm, 'd, 'p, 'utc, P: AsnDigest<'p>, S> {
view: &'s super::View<'a, 'm, 'd>,
later: &'s [crate::timestamp::historical::Later<'s, 'm, 'd>],
provider: &'p P,
algorithms: &'s [Algorithm],
operation: &'s structured_asn1::Reader<'d>,
utc: &'s structured_pkix::temporal::LeapTable<'utc>,
detached: Option<&'s mut S>,
ready: Option<Eight<'p, P>>,
}
impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> Lazy<'_, '_, '_, '_, 'p, '_, P, S> {
fn ready<W: Work>(
&mut self,
work: &W,
) -> Result<&Eight<'p, P>, InputError<S::Error, P::Error>> {
if self.ready.is_none() {
self.ready = Some(self.view.components_in(
self.provider,
self.algorithms,
self.operation,
self.detached.take(),
self.utc,
self.later,
work,
)?);
}
Ok(self
.ready
.as_ref()
.expect("successful component construction"))
}
}
impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> RequestSource<'p, P>
for Lazy<'_, '_, '_, '_, 'p, '_, P, S>
{
type Error = InputError<S::Error, P::Error>;
fn stream_request<W: Work>(
&mut self,
ordinal: usize,
provider: &'p P,
identity: &P::Identity,
out: &mut impl Receiver,
work: &W,
) -> Result<(), Self::Error> {
self.ready(work)?
.stream(ordinal, provider, identity, out, work)
}
}
impl<'p, P: AsnDigest<'p>, S: structured_cms::stream::Source> structured_cms::stream::Source
for Lazy<'_, '_, '_, '_, 'p, '_, P, S>
{
type Error = InputError<S::Error, P::Error>;
fn stream<W: Work>(&mut self, out: &mut impl Receiver, work: &W) -> Result<(), Self::Error> {
work.charge_work(1).map_err(|cause| {
InputError::Processing(Error::resource(
cause,
FIELD,
self.view.required().property.span().start,
))
})?;
if self.algorithms.len() != 1 {
return Err(InputError::Foreign);
}
let provider = self.provider;
let identity = provider
.identity(self.algorithms[0])
.ok_or(InputError::Unsupported)?;
self.ready(work)?.stream(0, provider, &identity, out, work)
}
}
#[cfg(test)]
#[path = "poe/tests.rs"]
mod tests;