1use super::{Error, Materials, Timestamp, TimestampKind, Writer};
4use crate::{
5 baseline::Assessment,
6 prepare::Prepared,
7 timestamp::{self, AppendKind, Input, TimestampXml},
8 unsigned::Requirements,
9 xml::Snapshot,
10};
11use structured_asn1::{
12 Reader,
13 memory::{Execution, Work},
14};
15use structured_cms::{
16 provider::SignatureProvider,
17 timestamp::{CheckContext, ProfileCheckError, TokenExtension},
18};
19
20pub trait Transaction: Writer {
26 type Id: Copy + Eq;
28 type Document: Copy + Eq;
30 type Generation: Copy + Eq;
32 fn base(&self) -> Snapshot<Self::Document, Self::Generation>;
34 fn signature(&self) -> Self::Id;
36 fn unsigned_parent(&self) -> Option<Self::Id>;
38 fn method<W: Work>(&mut self, method: Self::Id, work: &W) -> Result<(), Self::Error>;
42}
43
44pub struct Augmentation<'a, X: TimestampXml> {
48 xml: &'a X,
49 prepared: &'a Prepared<X>,
50}
51
52pub struct ResponseCheck<'a, 'context, P> {
55 pub response: &'a structured_cms::timestamp::Response<'a>,
57 pub provider: &'a P,
59 pub query: &'context Reader<'a>,
61 pub context: &'context CheckContext<'a, 'context>,
63}
64
65pub type Presence<'view, 'source, X> = structured_ltv::material::Incorporation<
67 'view,
68 'source,
69 <X as crate::xml::Xml>::Id,
70 <X as crate::xml::Xml>::Document,
71 <X as crate::xml::Xml>::Generation,
72 crate::material::Placement<<X as crate::xml::Xml>::Id>,
73>;
74pub type ResponseResult<T, X, PE, RE> =
76 Result<T, ResponseError<<X as crate::xml::Xml>::Error, <X as crate::xml::Xml>::Id, PE, RE>>;
77pub type CheckedFor<'a, X> = Checked<
79 'a,
80 <X as crate::xml::Xml>::Document,
81 <X as crate::xml::Xml>::Generation,
82 <X as crate::xml::Xml>::Id,
83>;
84pub type InputResult<'a, X> = Result<
86 Input<'a, X>,
87 PreparationError<<X as crate::xml::Xml>::Error, <X as crate::xml::Xml>::Id>,
88>;
89impl<'a, X: TimestampXml> Augmentation<'a, X> {
90 #[must_use]
92 pub const fn new(xml: &'a X, prepared: &'a Prepared<X>) -> Self {
93 Self { xml, prepared }
94 }
95
96 pub fn input<A, W: Work>(
100 &self,
101 assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
102 kind: TimestampKind,
103 method: X::Id,
104 work: &W,
105 ) -> InputResult<'a, X> {
106 self.gate(assessment, kind, work)
107 .map_err(PreparationError::Admission)?;
108 Input::prepare_append(
109 self.xml,
110 self.prepared.bound(),
111 append_kind(kind),
112 method,
113 work,
114 )
115 .map_err(PreparationError::Input)
116 }
117
118 #[expect(
124 clippy::result_large_err,
125 reason = "preserve original common profile/source/resource causes without allocating an error while handling exhaustion"
126 )]
127 pub fn check_response<'q, A, P, PE, RE>(
128 &'q self,
129 assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
130 kind: TimestampKind,
131 method: X::Id,
132 check: &ResponseCheck<'q, '_, P>,
133 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
134 ) -> ResponseResult<CheckedFor<'q, X>, X, PE, RE>
135 where
136 'a: 'q,
137 P: for<'scope> SignatureProvider<'scope, Error = PE>,
138 {
139 let work = check.query.execution();
140 self.gate(assessment, kind, &work)
141 .map_err(ResponseError::Admission)?;
142 let mut input = Input::prepare_append(
143 self.xml,
144 self.prepared.bound(),
145 append_kind(kind),
146 method,
147 &work,
148 )
149 .map_err(ResponseError::Input)?;
150 let facts = check
151 .response
152 .check_with_profile(
153 check.provider,
154 check.query,
155 check.context,
156 &mut input,
157 recognizes,
158 |input| timestamp::profile::check(&input),
159 )
160 .map_err(ResponseError::Check)?;
161 Ok(Checked {
162 snapshot: self.prepared.bound().containers().snapshot(),
163 signature: self.prepared.bound().containers().signature(),
164 method,
165 kind,
166 response: check.response,
167 facts,
168 })
169 }
170
171 pub fn write_timestamp<B, W: Work>(
175 &self,
176 timestamp: &Timestamp<'_, X::Document, X::Generation, X::Id>,
177 writer: &mut B,
178 work: &W,
179 ) -> Result<(), Error<B::Error>>
180 where
181 B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
182 {
183 self.require(writer, work)?;
184 let layout = self.prepared.bound().containers();
185 work.charge_work(1).map_err(Error::Resource)?;
186 if timestamp.snapshot != layout.snapshot()
187 || !structured_ltv::source::equal_id(timestamp.signature, layout.signature(), work)
188 .map_err(Error::Resource)?
189 {
190 return Err(Error::Foreign);
191 }
192 timestamp.write(writer, work, |writer, work| {
193 writer.method(timestamp.method, work)
194 })?;
195 self.require(writer, work)
196 }
197
198 pub fn write_materials<'view, 'source, B, W: Work>(
202 &self,
203 required: &Presence<'view, 'source, X>,
204 writer: &mut B,
205 work: &W,
206 envelope: impl FnMut(
207 &'view structured_pkix::ocsp::BasicResponse<'source>,
208 &Execution<'_>,
209 )
210 -> Result<&'view structured_pkix::ocsp::ResponseBytes<'source>, B::Error>,
211 ) -> Result<(), Error<B::Error>>
212 where
213 B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
214 {
215 self.require(writer, work)?;
216 let layout = self.prepared.bound().containers();
217 work.charge_work(1).map_err(Error::Resource)?;
218 if required.snapshot() != layout.snapshot()
219 || !structured_ltv::source::equal_id(required.signature(), layout.signature(), work)
220 .map_err(Error::Resource)?
221 {
222 return Err(Error::Foreign);
223 }
224 let materials = Materials::missing(required, work, envelope)?;
225 materials.write(writer, work)?;
226 self.require(writer, work)
227 }
228
229 fn gate<A, W: Work>(
230 &self,
231 assessment: &Assessment<'_, A, X::Id, X::Document, X::Generation>,
232 kind: TimestampKind,
233 work: &W,
234 ) -> Result<(), Error<core::convert::Infallible>> {
235 work.charge_work(1).map_err(Error::Resource)?;
236 if self.xml.snapshot() != self.prepared.bound().containers().snapshot()
237 || !core::ptr::eq(assessment.evidence().prepared(), self.prepared)
238 {
239 return Err(Error::Foreign);
240 }
241 if !assessment.all_requirements_met() {
242 return Err(Error::Prerequisites);
243 }
244 if kind == TimestampKind::Archive
245 && !matches!(
246 assessment.requested(),
247 Requirements::LongTerm | Requirements::Archival
248 )
249 {
250 return Err(Error::Prerequisites);
251 }
252 Ok(())
253 }
254
255 fn require<B, W: Work>(&self, writer: &B, work: &W) -> Result<(), Error<B::Error>>
256 where
257 B: Transaction<Id = X::Id, Document = X::Document, Generation = X::Generation>,
258 {
259 let layout = self.prepared.bound().containers();
260 work.charge_work(2).map_err(Error::Resource)?;
261 if self.xml.snapshot() != layout.snapshot()
262 || writer.base() != layout.snapshot()
263 || !structured_ltv::source::equal_id(writer.signature(), layout.signature(), work)
264 .map_err(Error::Resource)?
265 || !super::same_parent(
266 writer.unsigned_parent(),
267 self.prepared.unsigned().parent(),
268 work,
269 )
270 .map_err(Error::Resource)?
271 {
272 return Err(Error::Foreign);
273 }
274 Ok(())
275 }
276}
277
278fn append_kind(kind: TimestampKind) -> AppendKind {
279 match kind {
280 TimestampKind::Signature => AppendKind::Signature,
281 TimestampKind::Archive => AppendKind::Archive,
282 }
283}
284
285pub struct Checked<'a, D, G, N> {
289 snapshot: Snapshot<D, G>,
290 signature: N,
291 method: N,
292 kind: TimestampKind,
293 response: &'a structured_cms::timestamp::Response<'a>,
294 facts: structured_cms::timestamp::response::CheckedResponse<'a>,
295}
296impl<D: Copy, G: Copy, N: Copy> Checked<'_, D, G, N> {
297 #[must_use]
299 pub fn facts(&self) -> &structured_cms::timestamp::response::CheckedResponse<'_> {
300 &self.facts
301 }
302 #[must_use]
305 pub fn timestamp(&self) -> Option<Timestamp<'_, D, G, N>> {
306 let evidence = self.facts.timestamp()?.evidence()?;
307 let token = self.response.token_element()?;
308 let producer = self
309 .response
310 .token()?
311 .signer()
312 .digest_algorithm()
313 .identifier()?
314 .execution_origin();
315 Some(Timestamp {
316 snapshot: self.snapshot,
317 signature: self.signature,
318 method: self.method,
319 kind: self.kind,
320 token,
321 producer,
322 evidence,
323 })
324 }
325}
326#[derive(Debug)]
328pub enum PreparationError<E, N> {
329 Admission(Error<core::convert::Infallible>),
331 Input(timestamp::InputError<E, N>),
333}
334#[derive(Debug)]
336pub enum ResponseError<E, N, PE, RE> {
337 Admission(Error<core::convert::Infallible>),
339 Input(timestamp::InputError<E, N>),
341 Check(ProfileCheckError<timestamp::InputError<E, N>, PE, RE, timestamp::DigestError>),
343}