Skip to main content

structured_asn1/reader/
integer.rs

1//! Explicit recovery of numeric value without repairing the received encoding.
2
3use super::{Element, ScopedReader};
4use crate::{Error, ErrorCode, Integer, Limit, Result, value};
5
6/// An INTEGER's original contents, numeric view and encoding diagnostic.
7///
8/// The original bytes are never rewritten. `value()` borrows a minimal suffix
9/// of those bytes so its numeric conversions inspect at most eight significant
10/// octets. A nonminimal source is still malformed BER/DER (X.690 §8.3.2).
11/// Only the diagnostic path owns storage, retaining its account until drop.
12#[derive(Debug)]
13pub struct IntegerReading<'a> {
14    original: &'a [u8],
15    value: Integer<'a>,
16    fault: Option<Error>,
17}
18
19impl<'a> IntegerReading<'a> {
20    /// Original two's-complement contents, including redundant sign octets.
21    #[must_use]
22    pub fn as_bytes(&self) -> &'a [u8] {
23        self.original
24    }
25
26    /// Borrowed minimal numeric view; obtaining it performs no padding scan.
27    #[must_use]
28    pub fn value(&self) -> Integer<'a> {
29        self.value
30    }
31
32    /// Original located minimality violation; `None` means minimal contents.
33    #[must_use]
34    pub fn fault(&self) -> Option<&Error> {
35        self.fault.as_ref()
36    }
37
38    /// Move the numeric view and diagnostic without copying their storage.
39    ///
40    /// Retain `as_bytes()` or the original element separately when wire
41    /// identity is needed. The minimal view is not a replacement signed value.
42    #[must_use]
43    pub fn into_parts(self) -> (Integer<'a>, Option<Error>) {
44        (self.value, self.fault)
45    }
46}
47
48impl<'a: 'operation, 'operation> ScopedReader<'a, 'operation> {
49    /// Recover the unique numeric value while retaining nonminimal spelling.
50    ///
51    /// Unlike [`Self::integer`], this explicit diagnostic operation reads
52    /// redundant sign octets. It does not declare them conforming: X.690
53    /// §§8.3.1–8.3.3 requires primitive, nonempty, minimal two's complement
54    /// already under BER. Schema layers decide how to retain the fault.
55    /// <https://www.itu.int/rec/T-REC-X.690-202102-I/en>
56    ///
57    /// Use the Reader which produced `element`. Like the strict reader, this
58    /// supports primitive IMPLICIT tagging and leaves tag selection to the
59    /// schema. Original offsets, depth and cumulative local work are retained;
60    /// future work and diagnostic storage use the current execution account.
61    /// Payload bytes remain borrowed. Padding is scanned once, before any
62    /// bounded numeric conversion requested on the returned view.
63    ///
64    /// ```
65    /// use std::sync::Arc;
66    /// use structured_asn1::{Parser, ErrorCode};
67    /// use structured_asn1::memory::{Memory, MemoryAccount};
68    /// let memory = Memory::new(Arc::new(MemoryAccount::new(1024, 1024)));
69    /// let parser = Parser::der(memory);
70    /// let mut reader = parser.read(&[2, 2, 0, 1], 400)?;
71    /// let element = reader.next_element()?.unwrap();
72    /// let reading = reader.integer_reading(&element)?;
73    /// assert_eq!(reading.as_bytes(), &[0, 1]);
74    /// let (value, fault) = reading.into_parts();
75    /// assert_eq!(value.as_u64(), Some(1));
76    /// let fault = fault.unwrap();
77    /// assert_eq!(fault.code(), ErrorCode::IntegerNotMinimal);
78    /// assert_eq!(fault.offset(), 400);
79    /// # Ok::<(), structured_asn1::Error>(())
80    /// ```
81    ///
82    /// # Errors
83    ///
84    /// Empty and constructed contents remain errors. Work is admitted before
85    /// scanning; failure to retain the fault path is a resource refusal, never
86    /// a successful reading without its diagnostic.
87    pub fn integer_reading(&self, element: &Element<'a>) -> Result<IntegerReading<'a>> {
88        self.primitive(element)?;
89        // Fixed minimality/sign inspection, including the canonical fast path.
90        self.budget()
91            .work_in(3, self.memory())
92            .map_err(|limit| self.fail_under(ErrorCode::LimitExceeded(limit), element))?;
93        let original = element.contents();
94        match value::integer(original) {
95            Ok(value) => Ok(IntegerReading {
96                original,
97                value,
98                fault: None,
99            }),
100            Err(ErrorCode::IntegerNotMinimal) => {
101                // At most two byte inspections per removed sign octet, plus
102                // the final predicate. Overflow refuses before any traversal.
103                let work = u64::try_from(original.len())
104                    .ok()
105                    .and_then(|n| n.checked_mul(2))
106                    .and_then(|n| n.checked_add(1))
107                    .ok_or_else(|| {
108                        self.fail_under(ErrorCode::LimitExceeded(Limit::Work), element)
109                    })?;
110                self.budget()
111                    .work_in(work, self.memory())
112                    .map_err(|limit| self.fail_under(ErrorCode::LimitExceeded(limit), element))?;
113                let value = value::recovered_integer(original);
114                let fault = self.fail_under(ErrorCode::IntegerNotMinimal, element);
115                if fault.is_resource() {
116                    return Err(fault);
117                }
118                Ok(IntegerReading {
119                    original,
120                    value,
121                    fault: Some(fault),
122                })
123            }
124            Err(code) => Err(self.fail_under(code, element)),
125        }
126    }
127}