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 = "failure retains the original carried-source mapping rather than allocating or discarding provenance"
135 )]
136 pub async fn check_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_async_input_with_profile(
150 provider,
151 parent,
152 context,
153 &mut AsyncRawInput::new(covered),
154 recognizes,
155 |_| Ok::<(), ProfileFailure<Infallible>>(()),
156 CheckPurpose::Ordinary,
157 )
158 .await
159 .map_err(ProfileCheckError::into_common)
160 }
161
162 #[expect(
165 clippy::result_large_err,
166 reason = "profile failure preserves the scoped input map and original callback cause without an error allocation"
167 )]
168 pub async fn check_async_with_profile<'a, P, I, PE, RE, FE>(
169 &'a self,
170 provider: &'a P,
171 parent: &Reader<'a>,
172 context: &CheckContext<'a, '_>,
173 covered: &mut I,
174 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
175 profile: impl for<'view, 'scope> FnOnce(
176 ProfileInput<'view, 'doc, 'a, 'scope>,
177 ) -> Result<(), ProfileFailure<FE>>,
178 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
179 where
180 'doc: 'a,
181 P: for<'scope> SignatureProvider<'scope, Error = PE>,
182 I: AsyncComputedInput<P, PE>,
183 {
184 self.check_async_input_with_profile(
185 provider,
186 parent,
187 context,
188 covered,
189 recognizes,
190 profile,
191 CheckPurpose::Ordinary,
192 )
193 .await
194 }
195
196 #[expect(
199 clippy::result_large_err,
200 reason = "historical checking retains identical source-qualified failures to ordinary checking"
201 )]
202 pub async fn check_historical_async_with_profile<'a, P, I, PE, RE, FE>(
203 &'a self,
204 provider: &'a P,
205 parent: &Reader<'a>,
206 context: &CheckContext<'a, '_>,
207 covered: &mut I,
208 recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
209 profile: impl for<'view, 'scope> FnOnce(
210 ProfileInput<'view, 'doc, 'a, 'scope>,
211 ) -> Result<(), ProfileFailure<FE>>,
212 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
213 where
214 'doc: 'a,
215 P: for<'scope> SignatureProvider<'scope, Error = PE>,
216 I: AsyncComputedInput<P, PE>,
217 {
218 if context.requested.is_some() || context.reference.is_some() || context.window.is_some() {
219 return Err(CheckError::Processing(Error::schema(
220 ErrorCode::InvalidLocation,
221 "tstInfo.historical",
222 self.message().span().start,
223 ))
224 .into());
225 }
226 self.check_async_input_with_profile(
227 provider,
228 parent,
229 context,
230 covered,
231 recognizes,
232 profile,
233 CheckPurpose::Historical,
234 )
235 .await
236 }
237
238 #[expect(
239 clippy::result_large_err,
240 reason = "the one shared async checking pipeline owns its error source map after temporary input is dropped"
241 )]
242 #[expect(
243 clippy::too_many_arguments,
244 reason = "the shared driver receives independent provider, source, context, recognizer, format callback and historical purpose without boxing callbacks"
245 )]
246 async fn check_async_input_with_profile<'a, P, I, PE, RE, FE>(
247 &'a self,
248 provider: &'a P,
249 parent: &Reader<'a>,
250 context: &CheckContext<'a, '_>,
251 covered: &mut I,
252 mut recognizes: impl FnMut(&TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
253 profile: impl for<'view, 'scope> FnOnce(
254 ProfileInput<'view, 'doc, 'a, 'scope>,
255 ) -> Result<(), ProfileFailure<FE>>,
256 purpose: CheckPurpose,
257 ) -> Result<CheckedTimestamp<'a>, ProfileCheckError<I::Error, PE, RE, FE>>
258 where
259 'doc: 'a,
260 P: for<'scope> SignatureProvider<'scope, Error = PE>,
261 I: AsyncComputedInput<P, PE>,
262 {
263 preflight(self, parent, context).map_err(CheckError::from)?;
264 let identifier =
265 directory::matches_certificate_in(self.signer().sid(), context.certificate, parent)
266 .map_err(CheckError::Identifier)?;
267 let ess =
268 ess::SigningAttributes::read(self.signer(), parent).map_err(CheckError::Processing)?;
269 let carried = context
270 .requested
271 .as_ref()
272 .map(|requested| {
273 self.matches_cert_req(
274 requested.request,
275 Some(context.certificate),
276 parent,
277 requested.parent,
278 )
279 })
280 .transpose()
281 .map_err(CheckError::RequestField)?;
282 if !self.faults().is_empty()
283 || !self.message().faults().is_empty()
284 || !self.signer().faults().is_empty()
285 || !ess.faults().is_empty()
286 || ess.v1().is_some_and(|reading| !reading.faults().is_empty())
287 || ess.v2().is_some_and(|reading| !reading.faults().is_empty())
288 {
289 return Err(CheckError::Nonconforming {
290 span: self.message().span(),
291 }
292 .into());
293 }
294 let (signature, bindings, information) = self
295 .with_info_async(
296 parent,
297 None,
298 &mut Inspection {
299 token: self,
300 provider,
301 parent,
302 context,
303 ess: &ess,
304 identifier: &identifier,
305 carried,
306 input: covered,
307 recognizes: &mut recognizes,
308 profile: Some(profile),
309 purpose,
310 },
311 )
312 .await
313 .map_err(profile::information_error)?;
314 Ok(CheckedTimestamp {
315 signature,
316 identifier,
317 ess,
318 bindings,
319 authority: information.authority,
320 imprint: information.imprint,
321 requested_imprint: information.requested_imprint,
322 requested_fields: information.requested_fields,
323 carried,
324 timeliness: information.timeliness,
325 accuracy_error: information.accuracy_error,
326 unknown_critical: information.unknown,
327 evidence: information.evidence,
328 })
329 }
330}