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, 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    /// Complete historical checking over original asynchronously acquired bytes.
130    /// This makes no live-request/current-clock assertion. The existing
131    /// historical preflight rejects request, reference or window context.
132    #[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    /// Complete common timestamp checking over asynchronously acquired bytes.
162    /// The only suspension is bounded source acquisition inside scoped `TSTInfo`;
163    /// no joined reader or temporary model escapes, including on cancellation.
164    #[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    /// Parse-once profile checking before provider/content callbacks, followed
195    /// by the same complete asynchronous common pipeline.
196    #[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    /// Historical asynchronous pass with the same explicit prohibition on
229    /// current-clock/live-request assertions as the resident archival API.
230    #[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}