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