Skip to main content

structured_cms/timestamp/
extension.rs

1//! `TSTInfo` extensions reuse structural PKIX grammar with inspection recovery.
2
3use super::record;
4use crate::{Error, ErrorCode, Octets, Result};
5use structured_asn1::memory::{Execution, OwnedVec, Work};
6use structured_asn1::{Element, Oid, Parser, Reader, Span, Tag};
7use structured_pkix::{error::CallbackError, extension};
8
9const FIELD: &str = "tstInfo.extensions";
10
11/// One original extension, with logical OCTET STRING content still borrowed.
12#[derive(Debug)]
13pub struct TokenExtension<'a> {
14    kind: Oid<'a>,
15    critical: bool,
16    value: Octets<'a>,
17    span: Span,
18    value_span: Span,
19}
20
21impl<'a> TokenExtension<'a> {
22    fn read<W: Work>(
23        parent: &Reader<'a>,
24        element: &Element<'a>,
25        faults: &mut OwnedVec<Error, W>,
26    ) -> Result<Self> {
27        let fields = extension::open(parent, element, FIELD).map_err(Error::pkix)?;
28        let header = fields.header();
29        if header.default_written()
30            && let Some(span) = header.critical_span()
31        {
32            record(
33                faults,
34                Error::schema(ErrorCode::DefaultWritten, FIELD, span.start),
35            )?;
36        }
37        let reading = fields
38            .read_with(|_, reader, value| {
39                Octets::read(reader, value, FIELD).map(|octets| (octets, value.span()))
40            })
41            .map_err(|error| match error {
42                CallbackError::Processing(source) => Error::pkix(source),
43                CallbackError::Callback(source) => source,
44            })?;
45        if let Some(span) = reading.trailing_span() {
46            record(
47                faults,
48                Error::schema(ErrorCode::TrailingData, FIELD, span.start),
49            )?;
50        }
51        let (value, value_span) = reading.into_value();
52        Ok(Self {
53            kind: header.kind(),
54            critical: header.is_critical(),
55            value,
56            span: header.span(),
57            value_span,
58        })
59    }
60    /// Original extnID validated by the common OID decoder.
61    #[must_use]
62    pub fn kind(&self) -> Oid<'a> {
63        self.kind
64    }
65    /// Original critical flag or its schema-defined absent FALSE default.
66    #[must_use]
67    pub fn is_critical(&self) -> bool {
68        self.critical
69    }
70    /// Original logical value octets, without forced joined retention.
71    #[must_use]
72    pub fn value(&self) -> &Octets<'a> {
73        &self.value
74    }
75    /// Whole original Extension range.
76    #[must_use]
77    pub fn span(&self) -> Span {
78        self.span
79    }
80    /// Whole extnValue TLV, separately from content-fragment spans.
81    #[must_use]
82    pub fn value_span(&self) -> Span {
83        self.value_span
84    }
85}
86
87/// Recovered first readings plus independently owned source-located faults.
88/// These are inspection facts, not extension recognition or token acceptance.
89#[derive(Debug)]
90pub struct TokenExtensions<'a> {
91    entries: OwnedVec<TokenExtension<'a>>,
92    faults: OwnedVec<Error>,
93    span: Span,
94    parser: &'a Parser,
95}
96
97/// Recognition cannot erase an execution refusal or an opaque callback cause.
98#[derive(Debug)]
99pub enum RecognitionError<E> {
100    /// Original common processing/resource refusal.
101    Processing(Error),
102    /// Original extension handler failure at the actual extension enclosure.
103    Callback {
104        /// Original extension range in the producing reader's coordinates.
105        span: Span,
106        /// Original handler cause, including its own resource refusal.
107        source: E,
108    },
109}
110
111impl<'a> TokenExtensions<'a> {
112    /// Reads `[1] IMPLICIT Extensions` from the original producing reader.
113    ///
114    /// RFC3161 §2.4.2 uses RFC5280 Extension grammar but specifies no recovery.
115    /// This inspection contract retains equal logical duplicate values once
116    /// with faults and refuses conflicts; bounded unreadable members leave
117    /// independent later members visible. DEFAULT diagnostics precede deferred
118    /// value parsing, and trailing diagnostics follow it. The strict certificate
119    /// catalogue stays strict; it is not reused as a permissive token profile.
120    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
121    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
122        if element.tag() != Tag::context(true, 1) {
123            return Err(Error::schema(
124                ErrorCode::UnexpectedTag,
125                FIELD,
126                element.span().start,
127            ));
128        }
129        let mut inside = parent
130            .enter(element)
131            .map_err(|error| Error::encoding(error, FIELD))?;
132        let execution = parent.execution();
133        let mut entries: OwnedVec<TokenExtension<'a>, _> = execution.vector();
134        let mut faults = execution.vector();
135        while let Some(element) = inside
136            .next_element()
137            .map_err(|error| Error::encoding(error, FIELD))?
138        {
139            let candidate = TokenExtension::read(&inside, &element, &mut faults);
140            let candidate = match candidate {
141                Ok(candidate) => candidate,
142                Err(error) if error.is_terminal() => return Err(error),
143                Err(error) => {
144                    record(&mut faults, error)?;
145                    continue;
146                }
147            };
148            let at = candidate.span.start;
149            let mut duplicate = false;
150            for earlier in &entries {
151                execution
152                    .charge_work(1)
153                    .map_err(|reason| Error::resource(reason, FIELD, at))?;
154                if earlier.kind.as_bytes().len() != candidate.kind.as_bytes().len() {
155                    continue;
156                }
157                execution
158                    .charge_work(candidate.kind.as_bytes().len() as u64)
159                    .map_err(|reason| Error::resource(reason, FIELD, at))?;
160                if earlier.kind != candidate.kind {
161                    continue;
162                }
163                if earlier.critical != candidate.critical
164                    || !earlier
165                        .value
166                        .same_value(&candidate.value, &inside, FIELD, at)?
167                {
168                    return Err(Error::schema(ErrorCode::DuplicateField, FIELD, at));
169                }
170                record(
171                    &mut faults,
172                    Error::schema(ErrorCode::DuplicateField, FIELD, at),
173                )?;
174                duplicate = true;
175                break;
176            }
177            if !duplicate {
178                entries
179                    .try_push(candidate)
180                    .map_err(|reason| Error::resource(reason, FIELD, at))?;
181            }
182        }
183        if entries.is_empty() {
184            record(
185                &mut faults,
186                Error::schema(ErrorCode::MalformedValue, FIELD, element.span().start),
187            )?;
188        }
189        Ok(Self {
190            entries: entries.into_owned(),
191            faults: faults.into_owned(),
192            span: element.span(),
193            parser: parent.parser(),
194        })
195    }
196
197    /// Retained first readings in original order; no lookup/traversal is done.
198    #[must_use]
199    pub fn entries(&self) -> &[TokenExtension<'a>] {
200        &self.entries
201    }
202    /// Structural/recovery diagnostics, distinct from resource refusal.
203    #[must_use]
204    pub fn faults(&self) -> &[Error] {
205        &self.faults
206    }
207    /// Original implicit wrapper range.
208    #[must_use]
209    pub fn span(&self) -> Span {
210        self.span
211    }
212
213    /// Recognizes every critical token extension under current query work.
214    ///
215    /// RFC3161 §2.4.2 defines the extension structure but does not mandate a
216    /// client's understanding of all extension semantics. This explicit query
217    /// reports unrecognized critical entries for the caller's acceptance rule;
218    /// it does not assert that all noncritical entries are acceptable. Request
219    /// extensions have a different server-recognition contract (§2.4.1) and
220    /// are not processed by this method.
221    /// Each catalogue entry is paid before inspection, including noncritical
222    /// entries. The handler must admit its own data-dependent OID/value work;
223    /// this metadata charge cannot cover an arbitrary extension implementation.
224    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
225    pub fn unrecognized_critical<E>(
226        &self,
227        parent: &Reader<'_>,
228        mut recognizes: impl FnMut(&TokenExtension<'a>, &Execution<'_>) -> core::result::Result<bool, E>,
229    ) -> core::result::Result<OwnedVec<Oid<'a>>, RecognitionError<E>> {
230        if !core::ptr::eq(self.parser, parent.parser()) {
231            return Err(RecognitionError::Processing(Error::pkix(
232                structured_pkix::Error::new(
233                    structured_pkix::ErrorCode::ForeignReader,
234                    FIELD,
235                    self.span.start,
236                ),
237            )));
238        }
239        let work = parent.execution();
240        let mut unknown = work.vector();
241        for extension in &self.entries {
242            work.charge_work(1).map_err(|reason| {
243                RecognitionError::Processing(Error::resource(reason, FIELD, extension.span.start))
244            })?;
245            if !extension.critical {
246                continue;
247            }
248            if !recognizes(extension, &work).map_err(|source| RecognitionError::Callback {
249                span: extension.span,
250                source,
251            })? {
252                unknown.try_push(extension.kind).map_err(|reason| {
253                    RecognitionError::Processing(Error::resource(
254                        reason,
255                        FIELD,
256                        extension.span.start,
257                    ))
258                })?;
259            }
260        }
261        Ok(unknown.into_owned())
262    }
263}