Skip to main content

structured_pkix/
algorithm.rs

1//! Which algorithm, and the parameters that go with it.
2//!
3//! The identifier is kept as it was encoded and is not interpreted here. What
4//! may be verified with a key, and how, belongs to the algorithm registry; this
5//! layer's job is to hand that registry exactly what the certificate said,
6//! including parameters it does not understand.
7
8use crate::error::{Error, ErrorCode, Located, Result};
9use core::num::NonZeroUsize;
10use structured_asn1::memory::{Memory, Work};
11use structured_asn1::{Element, Encoding, Oid, Parser, Reader, ScopedReader, Span, Tag, universal};
12
13pub mod digest;
14pub mod pss;
15
16/// An algorithm and its parameters, as the certificate wrote them.
17#[derive(Debug, Clone, Copy)]
18pub struct AlgorithmIdentifier<'a> {
19    algorithm: Oid<'a>,
20    // A parameter follows the OID, so its relative start is nonzero. It is
21    // the last schema component; only an indefinite root's EOC follows it.
22    parameter_start: Option<NonZeroUsize>,
23    null_parameter: bool,
24    der: &'a [u8],
25    offset: u64,
26    field: &'static str,
27    parser: &'a Parser,
28    outer: u32,
29    encoding: Encoding,
30    indefinite: bool,
31}
32
33/// Original bytes nested one level inside an identifier's reader, carrying
34/// only what reading them in place needs: a key that inherits parameters
35/// keeps this rather than the authority's whole identifier.
36#[derive(Debug, Clone, Copy)]
37pub(crate) struct Nested<'a> {
38    bytes: &'a [u8],
39    at: u64,
40    parser: &'a Parser,
41    outer: u32,
42    encoding: Encoding,
43}
44
45impl<'a> Nested<'a> {
46    pub(crate) fn as_bytes(self) -> &'a [u8] {
47        self.bytes
48    }
49
50    /// Opens the single element these bytes hold. Source parser, offsets,
51    /// encoding and depth stay original; `execution` pays for the decoding.
52    pub(crate) fn open<'operation, W: Work>(
53        self,
54        execution: &'operation W,
55        field: &'static str,
56    ) -> Result<(ScopedReader<'a, 'operation>, Element<'a>)>
57    where
58        'a: 'operation,
59    {
60        let mut reader = execution
61            .read_scoped_nested(self.parser, self.bytes, self.at, self.encoding, self.outer)
62            .field(field)?;
63        let element = reader
64            .next_element()
65            .field(field)?
66            .ok_or_else(|| Error::new(ErrorCode::MissingField, field, reader.offset()))?;
67        if !reader.is_empty() {
68            return Err(Error::new(ErrorCode::TrailingData, field, reader.offset()));
69        }
70        Ok((reader, element))
71    }
72}
73
74// Raw schema fields are deliberately a different type from the validated
75// identifier. Only a fault-free reading can cross the crypto-facing boundary.
76#[derive(Debug, Clone, Copy)]
77struct AlgorithmFields<'a> {
78    algorithm: Oid<'a>,
79    parameter_start: Option<NonZeroUsize>,
80    null_parameter: bool,
81    der: &'a [u8],
82    offset: u64,
83    field: &'static str,
84    parser: &'a Parser,
85    outer: u32,
86    encoding: Encoding,
87    indefinite: bool,
88}
89
90/// Original algorithm fields plus a recoverable parameter-syntax diagnostic.
91///
92/// A malformed reading is inspection data, not valid BER/DER or a provider
93/// input. Only [`Self::identifier`] can expose a validated identifier. No input
94/// bytes are copied and the diagnostic itself has no allocated storage.
95///
96/// ```compile_fail
97/// use structured_pkix::algorithm::{AlgorithmIdentifier, AlgorithmReading};
98/// fn crypto_input(_: AlgorithmIdentifier<'_>) {}
99/// fn unchecked(reading: AlgorithmReading<'_>) {
100///     crypto_input(reading); // Inspection data is not a validated identifier.
101/// }
102/// ```
103#[derive(Debug)]
104pub struct AlgorithmReading<'a> {
105    fields: AlgorithmFields<'a>,
106    fault: Option<Error>,
107}
108
109impl<'a> AlgorithmReading<'a> {
110    /// The original, successfully decoded OID; not a supported capability.
111    #[must_use]
112    pub fn algorithm(&self) -> Oid<'a> {
113        self.fields.algorithm
114    }
115
116    /// The whole original parameter encoding, including any malformed form.
117    #[must_use]
118    pub fn parameters(&self) -> Option<&'a [u8]> {
119        parameter_bytes(
120            self.fields.der,
121            self.fields.parameter_start,
122            self.fields.indefinite,
123        )
124    }
125
126    /// Original parameter range in the producing reader's coordinates.
127    /// Joined content requires its caller-owned source map for physical ranges.
128    #[must_use]
129    pub fn parameters_span(&self) -> Option<Span> {
130        let start = self.fields.parameter_start?.get();
131        Some(Span {
132            start: self.fields.offset + start as u64,
133            length: self.parameters()?.len() as u64,
134        })
135    }
136
137    /// A primitive NULL whose complete parameter validation succeeded.
138    /// A malformed form, including constructed NULL, never has this fact.
139    #[must_use]
140    pub fn has_null_parameter(&self) -> bool {
141        self.fields.null_parameter
142    }
143
144    /// Whole original algorithm encoding, including an original IMPLICIT tag.
145    #[must_use]
146    pub fn as_bytes(&self) -> &'a [u8] {
147        self.fields.der
148    }
149
150    /// Original enclosing algorithm position in the producing source.
151    #[must_use]
152    pub fn offset(&self) -> u64 {
153        self.fields.offset
154    }
155
156    /// Encoding used by the producing reader, without a replacement parser.
157    #[must_use]
158    pub fn encoding(&self) -> Encoding {
159        self.fields.encoding
160    }
161
162    /// Whether this inspection reading belongs to the supplied parser session.
163    /// This checks original provenance even when no validated identifier exists;
164    /// account identity alone does not establish source/session identity.
165    #[must_use]
166    pub fn same_session(&self, parent: &Reader<'_>) -> bool {
167        core::ptr::eq(self.fields.parser, parent.parser())
168    }
169
170    /// Parameter syntax fault; resource and processing failures are never here.
171    #[must_use]
172    pub fn fault(&self) -> Option<&Error> {
173        self.fault.as_ref()
174    }
175
176    /// A validated identifier only when the complete parameter check succeeded.
177    /// Selected-provider parameter/profile acceptance still belongs to that provider.
178    #[must_use]
179    pub fn identifier(&self) -> Option<AlgorithmIdentifier<'a>> {
180        if self.fault.is_some() {
181            return None;
182        }
183        Some(self.fields.identifier())
184    }
185
186    /// Requires a validated provider input, returning the original syntax fault.
187    /// This is a scalar provenance check; no reparse, allocation or parameter
188    /// normalization is performed. Selected-provider acceptance is separate.
189    pub fn require_identifier(&self) -> Result<AlgorithmIdentifier<'a>> {
190        match &self.fault {
191            Some(error) => Err(error.clone()),
192            None => Ok(self.fields.identifier()),
193        }
194    }
195
196    /// Paid written-value identity for inspection, not parameter acceptance.
197    /// Malformed parameters retain exact encoded identity; only two validated
198    /// primitive NULL values can ignore their BER length spelling. Absence is
199    /// never inferred as a default. Both readings require this producing parser.
200    pub fn same_written_in(&self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
201        written_matches(&self.fields, &other.fields, parent, false)
202    }
203
204    fn into_identifier(self) -> Result<AlgorithmIdentifier<'a>> {
205        match self.fault {
206            Some(error) => Err(error),
207            None => Ok(self.fields.identifier()),
208        }
209    }
210}
211
212impl<'a> AlgorithmFields<'a> {
213    // Called only after the reading's fault gate. The raw type never escapes.
214    fn identifier(self) -> AlgorithmIdentifier<'a> {
215        AlgorithmIdentifier {
216            algorithm: self.algorithm,
217            parameter_start: self.parameter_start,
218            null_parameter: self.null_parameter,
219            der: self.der,
220            offset: self.offset,
221            field: self.field,
222            parser: self.parser,
223            outer: self.outer,
224            encoding: self.encoding,
225            indefinite: self.indefinite,
226        }
227    }
228}
229
230fn parameter_bytes(bytes: &[u8], start: Option<NonZeroUsize>, indefinite: bool) -> Option<&[u8]> {
231    let start = start?.get();
232    let end = bytes.len() - if indefinite { 2 } else { 0 };
233    Some(&bytes[start..end])
234}
235
236trait WrittenValue {
237    fn oid_bytes(&self) -> &[u8];
238    fn parameter_bytes(&self) -> Option<&[u8]>;
239    fn valid_null(&self) -> bool;
240    fn producer(&self) -> &Parser;
241    fn field(&self) -> &'static str;
242    fn offset(&self) -> u64;
243}
244
245impl WrittenValue for AlgorithmFields<'_> {
246    fn oid_bytes(&self) -> &[u8] {
247        self.algorithm.as_bytes()
248    }
249    fn parameter_bytes(&self) -> Option<&[u8]> {
250        parameter_bytes(self.der, self.parameter_start, self.indefinite)
251    }
252    fn valid_null(&self) -> bool {
253        self.null_parameter
254    }
255    fn producer(&self) -> &Parser {
256        self.parser
257    }
258    fn field(&self) -> &'static str {
259        self.field
260    }
261    fn offset(&self) -> u64 {
262        self.offset
263    }
264}
265
266impl WrittenValue for AlgorithmIdentifier<'_> {
267    fn oid_bytes(&self) -> &[u8] {
268        self.algorithm.as_bytes()
269    }
270    fn parameter_bytes(&self) -> Option<&[u8]> {
271        self.parameters()
272    }
273    fn valid_null(&self) -> bool {
274        self.null_parameter
275    }
276    fn producer(&self) -> &Parser {
277        self.parser
278    }
279    fn field(&self) -> &'static str {
280        self.field
281    }
282    fn offset(&self) -> u64 {
283        self.offset
284    }
285}
286
287fn require_written_parsers(
288    left: &impl WrittenValue,
289    right: &impl WrittenValue,
290    parent: &Reader<'_>,
291) -> Result<()> {
292    if !core::ptr::eq(left.producer(), parent.parser()) {
293        return Err(Error::new(
294            ErrorCode::ForeignReader,
295            left.field(),
296            left.offset(),
297        ));
298    }
299    if !core::ptr::eq(right.producer(), parent.parser()) {
300        return Err(Error::new(
301            ErrorCode::ForeignReader,
302            right.field(),
303            right.offset(),
304        ));
305    }
306    Ok(())
307}
308
309fn written_matches(
310    left: &impl WrittenValue,
311    right: &impl WrittenValue,
312    parent: &Reader<'_>,
313    defaults: bool,
314) -> Result<bool> {
315    require_written_parsers(left, right, parent)?;
316    written_values(left, right, parent, defaults)
317}
318
319fn written_values(
320    left: &impl WrittenValue,
321    right: &impl WrittenValue,
322    parent: &Reader<'_>,
323    defaults: bool,
324) -> Result<bool> {
325    let charge = |amount| {
326        parent.charge(amount).map_err(|error| {
327            Error::new(
328                ErrorCode::Encoding(error.code()),
329                right.field(),
330                right.offset(),
331            )
332        })
333    };
334    charge(1)?;
335    let kind = left.oid_bytes();
336    if kind.len() != right.oid_bytes().len() {
337        return Ok(false);
338    }
339    charge(kind.len() as u64)?;
340    if kind != right.oid_bytes() {
341        return Ok(false);
342    }
343    if defaults || (left.valid_null() && right.valid_null()) {
344        return Ok(true);
345    }
346    let (Some(first), Some(second)) = (left.parameter_bytes(), right.parameter_bytes()) else {
347        return Ok(left.parameter_bytes().is_none() && right.parameter_bytes().is_none());
348    };
349    if first.len() != second.len() {
350        return Ok(false);
351    }
352    charge(first.len() as u64)?;
353    Ok(first == second)
354}
355
356impl<'a> AlgorithmIdentifier<'a> {
357    /// Whether a written primitive NULL was validated by the producing reader.
358    /// Original parameter bytes remain unchanged; absence is not NULL here.
359    #[must_use]
360    pub fn has_null_parameter(self) -> bool {
361        self.null_parameter
362    }
363
364    /// Compares written values without assuming algorithm-specific defaults.
365    /// Two validated primitive NULLs share a value despite BER length spelling;
366    /// other parameters retain opaque encoded identity. Both identifiers must
367    /// come from this producing parser. This is not algorithm acceptance.
368    ///
369    /// # Errors
370    /// Returns a located resource refusal or a foreign-parser error before
371    /// comparing identifiers from another parsing session.
372    pub fn same_written_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
373        self.value_matches(other, parent, false)
374    }
375
376    /// Compares RFC5754 §2 SHA224/256/384/512 identifiers with absent/NULL defaults.
377    /// Unknown algorithms retain written-value semantics, not a guessed default.
378    /// Invalid known parameters and resource/caller refusals remain errors.
379    /// Selected-provider support and signature-profile acceptance are separate.
380    /// <https://www.rfc-editor.org/rfc/rfc5754#section-2>
381    ///
382    /// # Errors
383    /// Returns invalid known parameters, foreign-parser misuse, or resource
384    /// refusal separately from an ordinary identity mismatch.
385    pub fn digest_matches_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
386        self.value_matches(other, parent, true)
387    }
388
389    fn value_matches(self, other: &Self, parent: &Reader<'_>, digest: bool) -> Result<bool> {
390        require_written_parsers(&self, other, parent)?;
391        let sha2 = if digest {
392            let known = self.validate_digest_params(parent)?;
393            other.validate_digest_params(parent)?;
394            known
395        } else {
396            false
397        };
398        written_values(&self, other, parent, sha2)
399    }
400
401    fn validate_digest_params(self, parent: &Reader<'_>) -> Result<bool> {
402        let charge = |amount| {
403            parent.charge(amount).map_err(|error| {
404                Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
405            })
406        };
407        charge(1)?;
408        let kind = self.algorithm.as_bytes();
409        if kind.len() != 9 {
410            return Ok(false);
411        }
412        charge(9)?;
413        if kind[..8] != [0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2] || kind[8] < 1 || kind[8] > 4 {
414            return Ok(false);
415        }
416        if self.parameter_start.is_some() && !self.null_parameter {
417            // RFC5754 §2 permits only absent or NULL parameters for these four
418            // SHA2 identifiers. Unknown algorithms get no inferred default.
419            // https://www.rfc-editor.org/rfc/rfc5754#section-2
420            return Err(Error::new(
421                ErrorCode::MalformedValue,
422                self.field,
423                self.offset,
424            ));
425        }
426        Ok(true)
427    }
428    /// Which algorithm.
429    #[must_use]
430    pub fn algorithm(self) -> Oid<'a> {
431        self.algorithm
432    }
433
434    /// The parameters, as the whole encoded element, or `None` where the
435    /// certificate wrote none.
436    ///
437    /// Kept encoded because what they mean depends on the algorithm: the same
438    /// bytes are a curve for one and a hash function and salt length for
439    /// another.
440    #[must_use]
441    pub fn parameters(self) -> Option<&'a [u8]> {
442        parameter_bytes(self.der, self.parameter_start, self.indefinite)
443    }
444
445    /// Original algorithm element offset, without scanning or deriving a new range.
446    #[must_use]
447    pub fn offset(self) -> u64 {
448        self.offset
449    }
450
451    /// Encoding selected by the original schema reader for this identifier.
452    #[must_use]
453    pub fn encoding(self) -> Encoding {
454        self.encoding
455    }
456
457    /// Opens the whole original parameter TLV at its original reader depth.
458    ///
459    /// `parent` must belong to the original parser session; rebinding its
460    /// current execution account is allowed. A foreign session is rejected
461    /// before lookup, work or allocation. Both current funding and the original
462    /// cumulative session ceiling govern all later decoding. Absence is only
463    /// returned after admission, never as a resource fallback.
464    ///
465    /// The returned depth equals an immediate `inside.reread(parameter, encoding)`
466    /// after this identifier was read, including an IMPLICIT context envelope.
467    /// It includes the parameter header, not just its contents; no replacement
468    /// parser, canonical serialization or extra parameter nesting is introduced.
469    pub fn parameters_reader_in<'query>(
470        self,
471        parent: &Reader<'query>,
472        field: &'static str,
473    ) -> Result<Option<Reader<'query>>>
474    where
475        'a: 'query,
476    {
477        if !self.same_session(parent) {
478            return Err(Error::new(ErrorCode::ForeignReader, field, self.offset));
479        }
480        let at = self
481            .parameter_start
482            .map_or(self.offset, |start| self.offset + start.get() as u64);
483        parent
484            .execution()
485            .charge_work(1)
486            .map_err(|reason| Error::resource(reason, field, at))?;
487        let Some(parameter) = self.parameters() else {
488            return Ok(None);
489        };
490        self.parser
491            .read_nested_query_in(parent, parameter, at, self.encoding, self.outer)
492            .field(field)
493            .map(Some)
494    }
495
496    /// The written parameters, readable in place.
497    pub(crate) fn parameters_nested(self) -> Option<Nested<'a>> {
498        let start = self.parameter_start?.get();
499        Some(self.nested(self.parameters()?, self.offset + start as u64))
500    }
501
502    /// Original bytes at `at` that sit one level inside this identifier's
503    /// reader: its parameters, or the sibling key bits of an enclosing SPKI.
504    pub(crate) fn nested(self, bytes: &'a [u8], at: u64) -> Nested<'a> {
505        Nested {
506            bytes,
507            at,
508            parser: self.parser,
509            outer: self.outer,
510            encoding: self.encoding,
511        }
512    }
513
514    /// Whether this original identifier was produced by this parser session.
515    /// Account equality does not establish source/session identity. This O(1)
516    /// metadata predicate reads no parameters and spends no data-dependent work.
517    #[must_use]
518    pub fn same_session(self, parent: &ScopedReader<'_, '_>) -> bool {
519        core::ptr::eq(self.parser, parent.parser())
520    }
521
522    /// The whole identifier as it was encoded.
523    ///
524    /// For [`read_implicit`], this retains the original context tag, not a
525    /// synthesized SEQUENCE suitable for reparsing as an untagged identifier.
526    #[must_use]
527    pub fn as_bytes(self) -> &'a [u8] {
528        self.der
529    }
530
531    /// Whether the parameters are absent, or the null that means the same
532    /// thing for the algorithms that spell it that way.
533    #[must_use]
534    pub fn has_no_parameters(self) -> bool {
535        // The NULL validated under the document's own encoding, not the DER
536        // bytes `05 00`: BER may write its zero length in long form
537        // (X.690 §8.1.3.5, §8.8; RFC 5652 §1 makes a signed message BER).
538        self.parameters().is_none() || self.null_parameter
539    }
540
541    /// Whether two identifiers name the same algorithm with the same
542    /// parameters.
543    ///
544    /// An absent parameter and an explicit null are treated as the same, and
545    /// nothing else is. Issuers disagree about which of the two to write for
546    /// the algorithms that take no parameters, and both say the same thing;
547    /// any other difference changes what is being verified.
548    ///
549    /// The equivalence is deliberately not algorithm-specific. Which
550    /// parameter encodings an algorithm permits at all, including that the
551    /// Edwards-curve identifiers forbid the null form, is the algorithm
552    /// registry's decision, made against the identifier inside the signed
553    /// bytes. This comparison only asks whether the unsigned outer identifier
554    /// agrees with that signed one, and absent versus null cannot change
555    /// which algorithm that is: an outer null beside a signed absent is a
556    /// malformed spelling of the same identity, not a different algorithm
557    /// escaping the mismatch check.
558    ///
559    /// Deferred comparisons charge the supplied current operation. Their
560    /// resource diagnostics identify `self`'s original field and offset; they
561    /// do not assert that the two identifiers share a source. Parser-local
562    /// limits apply only when comparing through the producing reader.
563    ///
564    /// # Errors
565    /// Returns a resource refusal before an unfunded byte comparison.
566    ///
567    /// # Examples
568    /// ```
569    /// # extern crate alloc;
570    /// # use alloc::sync::Arc;
571    /// # use structured_asn1::{Parser, Tag, universal};
572    /// # use structured_asn1::memory::{Memory, MemoryAccount};
573    /// # let execution = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
574    /// # let parser = Parser::der(execution.clone());
575    /// # let mut reader = parser.read(b"\x30\x04\x06\x02\x2a\x03", 0).unwrap();
576    /// # let element = reader.expect(Tag::constructed(universal::SEQUENCE)).unwrap();
577    /// let identifier = structured_pkix::algorithm::read(&reader, &element, "algorithm")?;
578    /// assert!(identifier.matches(&identifier, &execution)?);
579    /// # Ok::<(), structured_pkix::Error>(())
580    /// ```
581    pub fn matches(self, other: &AlgorithmIdentifier<'_>, execution: &Memory) -> Result<bool> {
582        self.matches_with(other, |work| {
583            execution
584                .charge_work(work)
585                .map_err(|reason| Error::resource(reason, self.field, self.offset))
586        })
587    }
588
589    pub(crate) fn matches_in(
590        self,
591        other: &AlgorithmIdentifier<'_>,
592        parent: &Reader<'_>,
593    ) -> Result<bool> {
594        self.matches_with(other, |work| {
595            parent.charge(work).map_err(|error| {
596                Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
597            })
598        })
599    }
600
601    fn matches_with(
602        self,
603        other: &AlgorithmIdentifier<'_>,
604        mut charge: impl FnMut(u64) -> Result<()>,
605    ) -> Result<bool> {
606        charge(1)?;
607        if self.algorithm.as_bytes().len() != other.algorithm.as_bytes().len() {
608            return Ok(false);
609        }
610        charge(self.algorithm.as_bytes().len() as u64)?;
611        if self.algorithm != other.algorithm {
612            return Ok(false);
613        }
614        if self.has_no_parameters() && other.has_no_parameters() {
615            return Ok(true);
616        }
617        let (Some(left), Some(right)) = (self.parameters(), other.parameters()) else {
618            return Ok(false);
619        };
620        if left.len() != right.len() {
621            return Ok(false);
622        }
623        charge(left.len() as u64)?;
624        Ok(left == right)
625    }
626}
627
628/// Reads an algorithm identifier from an element a schema said is one.
629///
630/// It is public because certificates are not the only place this structure
631/// appears: a signed message names a digest and a signature algorithm the same
632/// way, and reading them a second time somewhere else would be a second set of
633/// rules about what parameters are allowed to look like.
634///
635/// # Errors
636///
637/// Reports the encoding fault and the field it was in.
638pub fn read<'a>(
639    parent: &ScopedReader<'a, '_>,
640    element: &Element<'a>,
641    field: &'static str,
642) -> Result<AlgorithmIdentifier<'a>> {
643    read_tagged(
644        parent,
645        element,
646        Tag::constructed(universal::SEQUENCE),
647        field,
648    )
649}
650
651/// Reads an original `[number] IMPLICIT AlgorithmIdentifier` without retagging.
652///
653/// The caller supplies the tag number selected by its trusted schema. For
654/// example, `CMSAlgorithmProtection` uses `[1]` for a signature and `[2]` for a
655/// MAC (RFC6211 §2); that cardinality/choice rule belongs to the CMS schema.
656/// The same inner algorithm/parameter core as [`read`] preserves original
657/// bytes, encoding, inherited depth and current producing execution ceilings.
658/// <https://www.rfc-editor.org/rfc/rfc6211#section-2>
659pub fn read_implicit<'a>(
660    parent: &Reader<'a>,
661    element: &Element<'a>,
662    number: u32,
663    field: &'static str,
664) -> Result<AlgorithmIdentifier<'a>> {
665    read_tagged(parent, element, Tag::context(true, number), field)
666}
667
668/// Inspects a uniquely shaped identifier while retaining parameter syntax faults.
669///
670/// The OID and at most one whole parameter must be unambiguous. Invalid
671/// parameter syntax is preserved as [`AlgorithmReading::fault`], never as a
672/// validated identifier. Resource/caller refusals, missing fields and ambiguous
673/// schema remain errors. This does not authorize malformed crypto input.
674pub fn read_inspection<'a>(
675    parent: &Reader<'a>,
676    element: &Element<'a>,
677    field: &'static str,
678) -> Result<AlgorithmReading<'a>> {
679    read_fields::<true>(
680        parent,
681        element,
682        Tag::constructed(universal::SEQUENCE),
683        field,
684    )
685}
686
687/// Original IMPLICIT counterpart of [`read_inspection`], sharing the same kernel.
688pub fn read_implicit_inspection<'a>(
689    parent: &Reader<'a>,
690    element: &Element<'a>,
691    number: u32,
692    field: &'static str,
693) -> Result<AlgorithmReading<'a>> {
694    read_fields::<true>(parent, element, Tag::context(true, number), field)
695}
696
697fn read_fields<'a, const INSPECTION: bool>(
698    parent: &ScopedReader<'a, '_>,
699    element: &Element<'a>,
700    expected: Tag,
701    field: &'static str,
702) -> Result<AlgorithmReading<'a>> {
703    let fail = |code| Error::new(code, field, element.span().start);
704    if element.tag() != expected {
705        return Err(fail(ErrorCode::Encoding(
706            structured_asn1::ErrorCode::UnexpectedTag,
707        )));
708    }
709    let mut inside = parent.enter(element).field(field)?;
710    let algorithm = inside.read_oid().field(field)?;
711    let parameter = inside.next_element().field(field)?;
712    let mut fault = None;
713    let mut null_parameter = false;
714    if let Some(parameter) = &parameter {
715        match crate::name::walk_der(&inside, parameter, field, crate::name::Strings::Checked) {
716            Ok(()) => {
717                // The shared walker validates primitive NULL contents. A
718                // constructed/nonempty NULL cannot acquire this cached fact.
719                // X.690 (02/2021) §8.8.1–2: primitive, with empty contents.
720                // https://www.itu.int/rec/T-REC-X.690-202102-I/en
721                null_parameter = parameter.tag() == Tag::primitive(universal::NULL);
722            }
723            Err(error) => {
724                if !INSPECTION || error.is_resource() || error.code() == ErrorCode::ForeignReader {
725                    return Err(error);
726                }
727                fault = Some(error);
728            }
729        }
730    }
731    if !inside.is_empty() {
732        return Err(fail(ErrorCode::TrailingData));
733    }
734    let parameter_start = match parameter {
735        None => None,
736        Some(parameter) => {
737            let start = usize::try_from(parameter.span().start - element.span().start)
738                .map_err(|_| fail(ErrorCode::ValueTooLarge))?;
739            Some(NonZeroUsize::new(start).expect("the algorithm OID precedes its parameters"))
740        }
741    };
742    Ok(AlgorithmReading {
743        fields: AlgorithmFields {
744            algorithm,
745            parameter_start,
746            null_parameter,
747            der: element.as_bytes(),
748            offset: element.span().start,
749            field,
750            parser: parent.parser(),
751            outer: parent.depth(),
752            encoding: inside.encoding(),
753            indefinite: element.is_indefinite(),
754        },
755        fault,
756    })
757}
758
759fn read_tagged<'a>(
760    parent: &ScopedReader<'a, '_>,
761    element: &Element<'a>,
762    expected: Tag,
763    field: &'static str,
764) -> Result<AlgorithmIdentifier<'a>> {
765    read_fields::<false>(parent, element, expected, field)?.into_identifier()
766}
767
768#[cfg(test)]
769mod tests;