1use super::{
4 CheckContext, CheckError, CheckPurpose, CheckedTimestamp, Error, ErrorCode, Execution,
5 Infallible, InfoCheckError, Information, InformationFacts, ProfileCheckError, ProfileFailure,
6 ProfileInput, Reader, SignatureProvider, SourceMap, Token, TokenExtension, TokenSignature,
7 common_holds, directory, ess, finish_information, preflight, profile, resolve_accuracy,
8 unrecognized,
9};
10use crate::timestamp::{AsyncComputedInput, AsyncInfoVisitor, AsyncRawInput, TokenInfo};
11
12struct Inspection<'a, 'doc, 'r, 'c, P, I, R, F> {
13 token: &'a Token<'doc>,
14 provider: &'a P,
15 parent: &'r Reader<'a>,
16 context: &'c CheckContext<'a, 'c>,
17 ess: &'r ess::SigningAttributes<'a>,
18 identifier: &'r directory::CertificateAgreement,
19 carried: Option<bool>,
20 input: &'r mut I,
21 recognizes: &'r mut R,
22 profile: Option<F>,
23 purpose: CheckPurpose,
24}
25
26impl<'a, 'doc: 'a, P, I, R, F, PE, RE, FE> AsyncInfoVisitor
27 for Inspection<'a, 'doc, '_, '_, P, I, R, F>
28where
29 P: for<'scope> SignatureProvider<'scope, Error = PE>,
30 I: AsyncComputedInput<P, PE>,
31 R: FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
32 F: for<'view, 'scope> FnOnce(
33 ProfileInput<'view, 'doc, 'a, 'scope>,
34 ) -> Result<(), ProfileFailure<FE>>,
35{
36 type Output = (TokenSignature<'a>, ess::Bindings, Information);
37 type Error = profile::InformationFailure<I::Error, PE, RE, FE>;
38
39 async fn visit<'data: 'scope, 'scope>(
40 &'scope mut self,
41 info: &'scope TokenInfo<'data>,
42 inside: &'scope Reader<'data>,
43 source: &'scope SourceMap,
44 ) -> Result<Self::Output, Self::Error> {
45 if info.has_faults() {
46 return Err(InfoCheckError::Nonconforming { span: info.span() }.into());
47 }
48 let accuracy =
49 resolve_accuracy::<I::Error, PE, RE>(info, inside, self.context.policy_accuracy)?;
50 self.profile.take().expect("one information visit")(ProfileInput {
51 token: self.token,
52 parent: self.parent,
53 envelope: None,
54 ess: self.ess,
55 info,
56 reader: inside,
57 source,
58 })
59 .map_err(profile::InformationFailure::Profile)?;
60 let bindings = self
61 .ess
62 .bind_certificate(self.provider, self.context.certificate, self.parent)
63 .map_err(InfoCheckError::Ess)?;
64 let signature: TokenSignature<'a> = self
65 .token
66 .check_signature(self.provider, self.context.key, self.parent)
67 .map_err(InfoCheckError::Signature)?;
68 let holds = common_holds(&signature, bindings, self.identifier, self.carried);
69 let authority = info
70 .authority(
71 self.context.certificate,
72 inside,
73 self.context.certificate_parent,
74 )
75 .map_err(InfoCheckError::Authority)?;
76 let fields = self
77 .context
78 .requested
79 .as_ref()
80 .map(|request| info.matches_request_fields(request.request, inside, request.parent))
81 .transpose()
82 .map_err(InfoCheckError::RequestField)?;
83 let unknown = unrecognized(info, inside, self.recognizes)?;
84 let (imprint, requested_imprint) = if let Some(request) = &self.context.requested {
85 let (imprint, requested) = info
86 .imprint()
87 .check_requested_input_async(
88 request.request.imprint(),
89 self.provider,
90 (inside, request.parent),
91 self.input,
92 )
93 .await
94 .map_err(|error| match error {
95 crate::timestamp::RequestedContentError::Request(source) => {
96 InfoCheckError::RequestImprint(source)
97 }
98 crate::timestamp::RequestedContentError::Content(source) => {
99 InfoCheckError::Imprint(source)
100 }
101 })?;
102 (imprint, Some(requested))
103 } else {
104 (
105 info.imprint()
106 .check_input_async(self.provider, inside, self.input)
107 .await
108 .map_err(InfoCheckError::Imprint)?,
109 None,
110 )
111 };
112 let information = finish_information(
113 info,
114 inside,
115 source,
116 self.context,
117 InformationFacts {
118 accuracy,
119 authority,
120 imprint,
121 requested_imprint,
122 requested_fields: fields,
123 unknown,
124 },
125 holds,
126 self.purpose,
127 )?;
128 Ok((signature, bindings, information))
129 }
130}
131
132impl<'doc> Token<'doc> {
133 #[expect(
137 clippy::result_large_err,
138 reason = "original information and source-map failures must survive resource refusal without a secondary allocation"
139 )]
140 pub async fn check_historical_async<'a, P, S, PE, RE>(
141 &'a self,
142 provider: &'a P,
143 parent: &Reader<'a>,
144 context: &CheckContext<'a, '_>,
145 covered: &mut S,
146 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
147 ) -> Result<CheckedTimestamp<'a>, CheckError<S::Error, PE, RE>>
148 where
149 'doc: 'a,
150 P: for<'scope> SignatureProvider<'scope, Error = PE>,
151 S: crate::stream::AsyncSource,
152 {
153 self.check_historical_async_with_profile(
154 provider,
155 parent,
156 context,
157 &mut AsyncRawInput::new(covered),
158 recognizes,
159 |_| Ok::<(), ProfileFailure<Infallible>>(()),
160 )
161 .await
162 .map_err(ProfileCheckError::into_common)
163 }
164
165 #[expect(
169 clippy::result_large_err,
170 reason = "failure retains the original carried-source mapping rather than allocating or discarding provenance"
171 )]
172 pub async fn check_async<'a, P, S, PE, RE>(
173 &'a self,
174 provider: &'a P,
175 parent: &Reader<'a>,
176 context: &CheckContext<'a, '_>,
177 covered: &mut S,
178 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
179 ) -> Result<CheckedTimestamp<'a>, CheckError<S::Error, PE, RE>>
180 where
181 'doc: 'a,
182 P: for<'scope> SignatureProvider<'scope, Error = PE>,
183 S: crate::stream::AsyncSource,
184 {
185 self.check_async_input_with_profile(
186 provider,
187 parent,
188 context,
189 &mut AsyncRawInput::new(covered),
190 recognizes,
191 |_| Ok::<(), ProfileFailure<Infallible>>(()),
192 CheckPurpose::Ordinary,
193 )
194 .await
195 .map_err(ProfileCheckError::into_common)
196 }
197
198 #[expect(
201 clippy::result_large_err,
202 reason = "profile failure preserves the scoped input map and original callback cause without an error allocation"
203 )]
204 pub async fn check_async_with_profile<'a, P, I, PE, RE, FE>(
205 &'a self,
206 provider: &'a P,
207 parent: &Reader<'a>,
208 context: &CheckContext<'a, '_>,
209 covered: &mut I,
210 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
211 profile: impl for<'view, 'scope> FnOnce(
212 ProfileInput<'view, 'doc, 'a, 'scope>,
213 ) -> Result<(), ProfileFailure<FE>>,
214 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
215 where
216 'doc: 'a,
217 P: for<'scope> SignatureProvider<'scope, Error = PE>,
218 I: AsyncComputedInput<P, PE>,
219 {
220 self.check_async_input_with_profile(
221 provider,
222 parent,
223 context,
224 covered,
225 recognizes,
226 profile,
227 CheckPurpose::Ordinary,
228 )
229 .await
230 }
231
232 #[expect(
235 clippy::result_large_err,
236 reason = "historical checking retains identical source-qualified failures to ordinary checking"
237 )]
238 pub async fn check_historical_async_with_profile<'a, P, I, PE, RE, FE>(
239 &'a self,
240 provider: &'a P,
241 parent: &Reader<'a>,
242 context: &CheckContext<'a, '_>,
243 covered: &mut I,
244 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
245 profile: impl for<'view, 'scope> FnOnce(
246 ProfileInput<'view, 'doc, 'a, 'scope>,
247 ) -> Result<(), ProfileFailure<FE>>,
248 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
249 where
250 'doc: 'a,
251 P: for<'scope> SignatureProvider<'scope, Error = PE>,
252 I: AsyncComputedInput<P, PE>,
253 {
254 if context.requested.is_some() || context.reference.is_some() || context.window.is_some() {
255 return Err(CheckError::Processing(Error::schema(
256 ErrorCode::InvalidLocation,
257 "tstInfo.historical",
258 self.message().span().start,
259 ))
260 .into());
261 }
262 self.check_async_input_with_profile(
263 provider,
264 parent,
265 context,
266 covered,
267 recognizes,
268 profile,
269 CheckPurpose::Historical,
270 )
271 .await
272 }
273
274 #[expect(
275 clippy::result_large_err,
276 reason = "the one shared async checking pipeline owns its error source map after temporary input is dropped"
277 )]
278 #[expect(
279 clippy::too_many_arguments,
280 reason = "the shared driver receives independent provider, source, context, recognizer, format callback and historical purpose without boxing callbacks"
281 )]
282 async fn check_async_input_with_profile<'a, P, I, PE, RE, FE>(
283 &'a self,
284 provider: &'a P,
285 parent: &Reader<'a>,
286 context: &CheckContext<'a, '_>,
287 covered: &mut I,
288 mut recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
289 profile: impl for<'view, 'scope> FnOnce(
290 ProfileInput<'view, 'doc, 'a, 'scope>,
291 ) -> Result<(), ProfileFailure<FE>>,
292 purpose: CheckPurpose,
293 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
294 where
295 'doc: 'a,
296 P: for<'scope> SignatureProvider<'scope, Error = PE>,
297 I: AsyncComputedInput<P, PE>,
298 {
299 preflight(self, parent, context).map_err(CheckError::from)?;
300 let identifier =
301 directory::matches_certificate_in(self.signer().sid(), context.certificate, parent)
302 .map_err(CheckError::Identifier)?;
303 let ess =
304 ess::SigningAttributes::read(self.signer(), parent).map_err(CheckError::Processing)?;
305 let carried = context
306 .requested
307 .as_ref()
308 .map(|requested| {
309 self.matches_cert_req(
310 requested.request,
311 Some(context.certificate),
312 parent,
313 requested.parent,
314 )
315 })
316 .transpose()
317 .map_err(CheckError::RequestField)?;
318 if !self.faults().is_empty()
319 || !self.message().faults().is_empty()
320 || !self.signer().faults().is_empty()
321 || !ess.faults().is_empty()
322 || ess.v1().is_some_and(|reading| !reading.faults().is_empty())
323 || ess.v2().is_some_and(|reading| !reading.faults().is_empty())
324 {
325 return Err(CheckError::Nonconforming {
326 span: self.message().span(),
327 }
328 .into());
329 }
330 let (signature, bindings, information) = self
331 .with_info_async(
332 parent,
333 None,
334 &mut Inspection {
335 token: self,
336 provider,
337 parent,
338 context,
339 ess: &ess,
340 identifier: &identifier,
341 carried,
342 input: covered,
343 recognizes: &mut recognizes,
344 profile: Some(profile),
345 purpose,
346 },
347 )
348 .await
349 .map_err(profile::information_error)?;
350 Ok(CheckedTimestamp {
351 signature,
352 identifier,
353 ess,
354 bindings,
355 authority: information.authority,
356 imprint: information.imprint,
357 requested_imprint: information.requested_imprint,
358 requested_fields: information.requested_fields,
359 carried,
360 timeliness: information.timeliness,
361 accuracy_error: information.accuracy_error,
362 unknown_critical: information.unknown,
363 evidence: information.evidence,
364 })
365 }
366}