Skip to main content

structured_cms/timestamp/check/
asynchronous.rs

1//! Asynchronous acquisition inside the same scoped timestamp check pipeline.
2
3use 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    /// Complete historical checking over original asynchronously acquired bytes.
134    /// This makes no live-request/current-clock assertion. The existing
135    /// historical preflight rejects request, reference or window context.
136    #[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    /// Complete common timestamp checking over asynchronously acquired bytes.
166    /// The only suspension is bounded source acquisition inside scoped `TSTInfo`;
167    /// no joined reader or temporary model escapes, including on cancellation.
168    #[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    /// Parse-once profile checking before provider/content callbacks, followed
199    /// by the same complete asynchronous common pipeline.
200    #[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    /// Historical asynchronous pass with the same explicit prohibition on
233    /// current-clock/live-request assertions as the resident archival API.
234    #[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}