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