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