structured_asn1/reader/integer.rs
1//! Explicit recovery of numeric value without repairing the received encoding.
2
3use super::{Element, Reader};
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> Reader<'a> {
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}