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