1use super::{InputError, Lazy};
3use crate::{
4 Error,
5 basic::PathRestriction,
6 evidence::{
7 PoeValue, View,
8 record::{Authorities, Checked, Selected, SelectionError},
9 },
10 timestamp::{self, ProfileError, VisitError},
11};
12use core::{
13 cell::{Cell, RefCell},
14 convert::Infallible,
15};
16use structured_asn1::{
17 Reader,
18 memory::{Execution, Work},
19};
20use structured_cms::{
21 evidence::{Membership, verify as ers},
22 provider::SignatureProvider,
23 stream::Source,
24 timestamp::{self as tsp, TokenExtension},
25};
26use structured_pkix::temporal::{Instant, LeapTable};
27const FIELD: &str = "cades.longTermValidation.check";
28
29type ErsFailure<SE, PE, RE, AE, HE> = ers::VerificationError<
30 InputError<SE, PE>,
31 PE,
32 RE,
33 SelectionError<AE>,
34 HE,
35 ProfileError<PE, Error>,
36>;
37#[derive(Debug)]
39pub enum CheckError<SE, PE, RE, AE, HE> {
40 Processing(Error),
42 Plan(ers::PlanError),
44 Authority(AE),
46 Record(ErsFailure<SE, PE, RE, AE, HE>),
48 Timestamp(tsp::ProfileCheckError<InputError<SE, PE>, PE, RE, ProfileError<PE, Error>>),
50 Historical(HE),
52}
53
54pub type CheckResult<'a, 'm, 'd, 'path, SE, PE, RE, AE, HE> =
56 Result<Checked<'a, 'm, 'd, 'path>, CheckError<SE, PE, RE, AE, HE>>;
57
58impl<'a, 'm, 'd> View<'a, 'm, 'd> {
59 #[expect(
69 clippy::too_many_arguments,
70 reason = "Actual payload acquisition, current execution, path/provider selection and historical validation are separate contracts."
71 )]
72 #[expect(
73 clippy::result_large_err,
74 reason = "Original source maps and opaque acquisition/provider/history failures survive allocation refusal inline."
75 )]
76 pub fn check_poe<'path, P, PE, A, S, RE, HE>(
77 &self,
78 provider: &P,
79 operation: &Reader<'d>,
80 authorities: &'path A,
81 utc: &LeapTable<'d>,
82 reference: Instant,
83 detached: Option<&mut S>,
84 recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
85 historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
86 ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
87 where
88 P: for<'scope> SignatureProvider<'scope, Error = PE>,
89 A: Authorities<'d>,
90 S: Source,
91 'd: 'path,
92 {
93 self.check_poe_with_later(
94 provider,
95 operation,
96 authorities,
97 utc,
98 reference,
99 detached,
100 &[],
101 recognizes,
102 historical,
103 )
104 }
105
106 #[expect(
111 clippy::too_many_arguments,
112 reason = "Sealed later-proof coverage supplements the independent source, execution and verification callbacks."
113 )]
114 #[expect(
115 clippy::result_large_err,
116 reason = "Neither the mapped ERS nor timestamp failure can be boxed on an exhausted operation budget."
117 )]
118 #[expect(
119 clippy::too_many_lines,
120 reason = "The two first-read formats share one atomic source/profile/history ledger and return no successful partial report."
121 )]
122 pub fn check_poe_with_later<'path, P, PE, A, S, RE, HE>(
123 &self,
124 provider: &P,
125 operation: &Reader<'d>,
126 authorities: &'path A,
127 utc: &LeapTable<'d>,
128 reference: Instant,
129 detached: Option<&mut S>,
130 later: &[crate::timestamp::historical::Later<'_, 'm, 'd>],
131 mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
132 mut historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
133 ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
134 where
135 P: for<'scope> SignatureProvider<'scope, Error = PE>,
136 A: Authorities<'d>,
137 S: Source,
138 'd: 'path,
139 {
140 let poe = self.poe().ok_or_else(|| {
141 CheckError::Processing(Error::Foreign {
142 at: self.required().property.span().start,
143 })
144 })?;
145 let required = self.required();
146 if required.placement != structured_cms::attribute::Placement::Unsigned {
147 return Err(CheckError::Processing(Error::Syntax {
148 code: structured_cms::ErrorCode::AttributePlacement,
149 field: FIELD,
150 at: required.property.span().start,
151 }));
152 }
153 let signer = self
154 .prepared
155 .signer_encoding(required.signature, operation)
156 .map_err(CheckError::Processing)?;
157 let mut single = false;
158 for ordinal in 0..signer.attributes() {
159 operation
160 .charge(1)
161 .map_err(|cause| CheckError::Processing(Error::encoding(cause, FIELD)))?;
162 let attribute =
163 signer
164 .attribute(ordinal)
165 .ok_or(CheckError::Processing(Error::Foreign {
166 at: required.property.span().start,
167 }))?;
168 if attribute.span == required.property.span() {
169 single = attribute.single.is_some()
170 && attribute.single == required.value.map(|value| value.element().span());
171 break;
172 }
173 }
174 if !single {
175 return Err(CheckError::Processing(Error::Syntax {
176 code: structured_cms::ErrorCode::AttributeCardinality,
177 field: FIELD,
178 at: required.property.span().start,
179 }));
180 }
181 let at = poe.element().span().start;
182 let resource = |cause| CheckError::Processing(Error::resource(cause, FIELD, at));
183 let work = operation.execution();
184 let proof = poe
185 .proof()
186 .ok_or(CheckError::Processing(Error::Foreign { at }))?;
187 match proof {
188 PoeValue::EvidenceRecord(record) => {
189 let plan = record.input_plan(operation).map_err(CheckError::Plan)?;
190 self.admit_poe_algorithms(plan.algorithms(), operation)
191 .map_err(CheckError::Processing)?;
192 let mut slots = work.vector();
193 let mut restrictions = work.vector();
194 slots
195 .try_reserve_exact(record.timestamps().len())
196 .map_err(resource)?;
197 restrictions
198 .try_reserve_exact(record.timestamps().len())
199 .map_err(resource)?;
200 for _ in record.timestamps() {
201 slots.try_push(None).map_err(resource)?;
202 restrictions
203 .try_push(Cell::new(PathRestriction::Unproven))
204 .map_err(resource)?;
205 }
206 let selected = Selected {
207 source: authorities,
208 start: self.entry.timestamps.start,
209 items: RefCell::new(slots.into_owned()),
210 };
211 let mut sources = [Lazy {
212 view: self,
213 later,
214 provider,
215 algorithms: plan.algorithms(),
216 operation,
217 utc,
218 detached,
219 ready: None,
220 }];
221 let report = plan
222 .verify_requests_with_profile(
223 provider,
224 operation,
225 &selected,
226 utc,
227 reference,
228 &mut sources,
229 Membership::Object,
230 |index, extension, execution| {
231 recognizes(self.entry.timestamps.start + index, extension, execution)
232 },
233 |mut facts, execution| {
234 facts.index += self.entry.timestamps.start;
235 historical(facts, execution)
236 },
237 |index, input| {
238 let path = selected
239 .path(index, &input.parent.execution(), at)
240 .map_err(|cause| tsp::ProfileFailure {
241 source: tsp::ProfileSource::Envelope,
242 cause: ProfileError::Input(cause),
243 })?;
244 restrictions[index]
245 .set(timestamp::Context::profile_rules(&input, path, provider)?);
246 Ok(())
247 },
248 )
249 .map_err(CheckError::Record)?;
250 let covered = sources[0].ready.take().map(|eight| eight.covered);
251 let mut checked = self
252 .finish_record(report, &selected, &restrictions, operation)
253 .map_err(CheckError::Processing)?;
254 checked
255 .bind_coverage(covered, &work)
256 .map_err(CheckError::Processing)?;
257 Ok(checked)
258 }
259 PoeValue::Timestamp(token) => {
260 let source = poe
261 .timestamp_source()
262 .ok_or(CheckError::Processing(Error::Foreign { at }))?;
263 let reader = source
264 .reader_in(operation)
265 .map_err(CheckError::Processing)?;
266 if !token.faults().is_empty()
269 || !token.message().faults().is_empty()
270 || !token.signer().faults().is_empty()
271 {
272 return Err(CheckError::Plan(ers::PlanError::Nonconforming {
273 index: self.entry.timestamps.start,
274 span: token.message().span(),
275 }));
276 }
277 let algorithm = ers::token_algorithm(token, &reader)
278 .map_err(|cause| CheckError::Plan(ers::PlanError::Information(cause)))?;
279 self.admit_poe_algorithms(&[algorithm], operation)
280 .map_err(CheckError::Processing)?;
281 let choice = authorities
282 .select(self.entry.timestamps.start, token, &reader.execution())
283 .map_err(CheckError::Authority)?;
284 let target = choice
285 .path
286 .members()
287 .last()
288 .ok_or(CheckError::Processing(Error::Foreign { at }))?;
289 let selection = ers::Selection {
290 certificate: target.certificate(),
291 certificate_parent: choice.certificate_parent,
292 key: target.key(),
293 policy_accuracy: choice.policy_accuracy,
294 };
295 let context = tsp::CheckContext {
296 certificate: selection.certificate,
297 certificate_parent: selection.certificate_parent,
298 key: selection.key,
299 requested: None,
300 utc,
301 reference: None,
302 window: None,
303 policy_accuracy: selection.policy_accuracy,
304 };
305 let mut covered = Lazy {
306 view: self,
307 later,
308 provider,
309 algorithms: &[algorithm],
310 operation,
311 utc,
312 detached,
313 ready: None,
314 };
315 let restriction = Cell::new(PathRestriction::Unproven);
316 let common = token
317 .check_historical_input_with_profile(
318 provider,
319 &reader,
320 &context,
321 &mut tsp::RawInput::new(&mut covered),
322 |extension, execution| {
323 recognizes(self.entry.timestamps.start, extension, execution)
324 },
325 |input| {
326 restriction.set(timestamp::Context::profile_rules(
327 &input,
328 choice.path,
329 provider,
330 )?);
331 Ok(())
332 },
333 )
334 .map_err(CheckError::Timestamp)?;
335 let history = historical(
336 ers::Historical {
337 index: self.entry.timestamps.start,
338 current: &common,
339 selection: &selection,
340 following: None,
341 following_chain: None,
342 verification_time: reference,
343 },
344 &reader.execution(),
345 )
346 .map_err(CheckError::Historical)?;
347 let mut complete = history == ers::Decision::Valid && common.evidence().is_some();
348 let checked = timestamp::with_token(
349 self.prepared,
350 self.entry.timestamps.start,
351 operation,
352 |context| {
353 Ok::<_, Infallible>(context.checked(
354 common,
355 restriction.get(),
356 target.certificate(),
357 target.key(),
358 ))
359 },
360 )
361 .map_err(|cause| {
362 CheckError::Processing(match cause {
363 VisitError::Processing(cause) => cause,
364 VisitError::Unreadable { property } => Error::Foreign {
365 at: property.span().start,
366 },
367 VisitError::Callback(never) => match never {},
368 })
369 })?;
370 if checked.actor().is_none() {
371 complete = false;
372 }
373 let mut checks = work.vector();
374 checks.try_push(checked).map_err(resource)?;
375 let spans = covered.ready.take().map(|eight| eight.covered);
376 let mut checked = Checked {
377 prepared: self.prepared,
378 required: self.required(),
379 proof: self.ordinal,
380 covered: None,
381 timestamps: self.entry.timestamps.clone(),
382 checks: checks.into_owned(),
383 ordering: work.vector().into_owned(),
384 complete,
385 };
386 checked
387 .bind_coverage(spans, &work)
388 .map_err(CheckError::Processing)?;
389 Ok(checked)
390 }
391 }
392 }
393}