use super::{Error, Materials, Timestamp, TimestampKind, Writer};
use crate::{
baseline::Assessment,
prepare::Prepared,
timestamp::{self, AppendKind, Input, TimestampXml},
unsigned::Requirements,
xml::Snapshot,
};
use structured_asn1::{
Reader,
memory::{Execution, Work},
};
use structured_cms::{
provider::SignatureProvider,
timestamp::{CheckContext, ProfileCheckError, TokenExtension},
};
pub trait Transaction: Writer {
type Id: Copy + Eq;
type Document: Copy + Eq;
type Generation: Copy + Eq;
fn base(&self) -> Snapshot<Self::Document, Self::Generation>;
fn signature(&self) -> Self::Id;
fn unsigned_parent(&self) -> Option<Self::Id>;
fn method<W: Work>(&mut self, method: Self::Id, work: &W) -> Result<(), Self::Error>;
}
pub struct Augmentation<'a, X: TimestampXml> {
xml: &'a X,
prepared: &'a Prepared<X>,
}
pub struct ResponseCheck<'a, 'context, P> {
pub response: &'a structured_cms::timestamp::Response<'a>,
pub provider: &'a P,
pub query: &'context Reader<'a>,
pub context: &'context CheckContext<'a, 'context>,
}
pub type Presence<'view, 'source, X> = structured_ltv::material::Incorporation<
'view,
'source,
<X as crate::xml::Xml>::Id,
<X as crate::xml::Xml>::Document,
<X as crate::xml::Xml>::Generation,
crate::material::Placement<<X as crate::xml::Xml>::Id>,
>;
pub type ResponseResult<T, X, PE, RE> =
Result<T, ResponseError<<X as crate::xml::Xml>::Error, <X as crate::xml::Xml>::Id, PE, RE>>;
pub type CheckedFor<'a, X> = Checked<
'a,
<X as crate::xml::Xml>::Document,
<X as crate::xml::Xml>::Generation,
<X as crate::xml::Xml>::Id,
>;
pub type InputResult<'a, X> = Result<
Input<'a, X>,
PreparationError<<X as crate::xml::Xml>::Error, <X as crate::xml::Xml>::Id>,
>;
impl<'a, X: TimestampXml> Augmentation<'a, X> {
#[must_use]
pub const fn new(xml: &'a X, prepared: &'a Prepared<X>) -> Self {
Self { xml, prepared }
}
pub fn input<A, W: Work>(
&self,
assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
kind: TimestampKind,
method: X::Id,
work: &W,
) -> InputResult<'a, X> {
self.gate(assessment, kind, work)
.map_err(PreparationError::Admission)?;
Input::prepare_append(
self.xml,
self.prepared.bound(),
append_kind(kind),
method,
work,
)
.map_err(PreparationError::Input)
}
#[expect(
clippy::result_large_err,
reason = "preserve original common profile/source/resource causes without allocating an error while handling exhaustion"
)]
pub fn check_response<'q, A, P, PE, RE>(
&'q self,
assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
kind: TimestampKind,
method: X::Id,
check: &ResponseCheck<'q, '_, P>,
recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
) -> ResponseResult<CheckedFor<'q, X>, X, PE, RE>
where
'a: 'q,
P: for<'scope> SignatureProvider<'scope, Error = PE>,
{
let work = check.query.execution();
self.gate(assessment, kind, &work)
.map_err(ResponseError::Admission)?;
let mut input = Input::prepare_append(
self.xml,
self.prepared.bound(),
append_kind(kind),
method,
&work,
)
.map_err(ResponseError::Input)?;
let facts = check
.response
.check_with_profile(
check.provider,
check.query,
check.context,
&mut input,
recognizes,
|input| timestamp::profile::check(&input),
)
.map_err(ResponseError::Check)?;
Ok(Checked {
snapshot: self.prepared.bound().containers().snapshot(),
signature: self.prepared.bound().containers().signature(),
method,
kind,
response: check.response,
facts,
})
}
pub fn write_timestamp<B, W: Work>(
&self,
timestamp: &Timestamp<'_, X::Document, X::Generation, X::Id>,
writer: &mut B,
work: &W,
) -> Result<(), Error<B::Error>>
where
B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
{
self.require(writer, work)?;
let layout = self.prepared.bound().containers();
work.charge_work(1).map_err(Error::Resource)?;
if timestamp.snapshot != layout.snapshot()
|| !structured_ltv::source::equal_id(timestamp.signature, layout.signature(), work)
.map_err(Error::Resource)?
{
return Err(Error::Foreign);
}
timestamp.write(writer, work, |writer, work| {
writer.method(timestamp.method, work)
})?;
self.require(writer, work)
}
pub fn write_materials<'view, 'source, B, W: Work>(
&self,
required: &Presence<'view, 'source, X>,
writer: &mut B,
work: &W,
envelope: impl FnMut(
&'view structured_pkix::ocsp::BasicResponse<'source>,
&Execution<'_>,
)
-> Result<&'view structured_pkix::ocsp::ResponseBytes<'source>, B::Error>,
) -> Result<(), Error<B::Error>>
where
B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
{
self.require(writer, work)?;
let layout = self.prepared.bound().containers();
work.charge_work(1).map_err(Error::Resource)?;
if required.snapshot() != layout.snapshot()
|| !structured_ltv::source::equal_id(required.signature(), layout.signature(), work)
.map_err(Error::Resource)?
{
return Err(Error::Foreign);
}
let materials = Materials::missing(required, work, envelope)?;
materials.write(writer, work)?;
self.require(writer, work)
}
fn gate<A, W: Work>(
&self,
assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
kind: TimestampKind,
work: &W,
) -> Result<(), Error<core::convert::Infallible>> {
work.charge_work(1).map_err(Error::Resource)?;
if self.xml.snapshot() != self.prepared.bound().containers().snapshot()
|| !core::ptr::eq(assessment.evidence().prepared(), self.prepared)
{
return Err(Error::Foreign);
}
if !assessment.all_requirements_met() {
return Err(Error::Prerequisites);
}
if kind == TimestampKind::Archive
&& !matches!(
assessment.requested(),
Requirements::LongTerm | Requirements::Archival
)
{
return Err(Error::Prerequisites);
}
Ok(())
}
fn require<B, W: Work>(&self, writer: &B, work: &W) -> Result<(), Error<B::Error>>
where
B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
{
let layout = self.prepared.bound().containers();
work.charge_work(2).map_err(Error::Resource)?;
if self.xml.snapshot() != layout.snapshot()
|| writer.base() != layout.snapshot()
|| !structured_ltv::source::equal_id(writer.signature(), layout.signature(), work)
.map_err(Error::Resource)?
|| !super::same_parent(
writer.unsigned_parent(),
self.prepared.unsigned().parent(),
work,
)
.map_err(Error::Resource)?
{
return Err(Error::Foreign);
}
Ok(())
}
}
fn append_kind(kind: TimestampKind) -> AppendKind {
match kind {
TimestampKind::Signature => AppendKind::Signature,
TimestampKind::Archive => AppendKind::Archive,
}
}
pub struct Checked<'a, D, G, N> {
snapshot: Snapshot<D, G>,
signature: N,
method: N,
kind: TimestampKind,
response: &'a structured_cms::timestamp::Response<'a>,
facts: structured_cms::timestamp::response::CheckedResponse<'a>,
}
impl<D: Copy, G: Copy, N: Copy> Checked<'_, D, G, N> {
#[must_use]
pub fn facts(&self) -> &structured_cms::timestamp::response::CheckedResponse<'_> {
&self.facts
}
#[must_use]
pub fn timestamp(&self) -> Option<Timestamp<'_, D, G, N>> {
let evidence = self.facts.timestamp()?.evidence()?;
let token = self.response.token_element()?;
let producer = self
.response
.token()?
.signer()
.digest_algorithm()
.identifier()?
.execution_origin();
Some(Timestamp {
snapshot: self.snapshot,
signature: self.signature,
method: self.method,
kind: self.kind,
token,
producer,
evidence,
})
}
}
#[derive(Debug)]
pub enum PreparationError<E, N> {
Admission(Error<core::convert::Infallible>),
Input(timestamp::InputError<E, N>),
}
#[derive(Debug)]
pub enum ResponseError<E, N, PE, RE> {
Admission(Error<core::convert::Infallible>),
Input(timestamp::InputError<E, N>),
Check(ProfileCheckError<timestamp::InputError<E, N>, PE, RE, timestamp::DigestError>),
}