Skip to main content

structured_cms/timestamp/
accuracy.rs

1//! Accuracy schema over original integer views and independently owned faults.
2
3use super::{Accuracy, record};
4use crate::{Error, ErrorCode, Result};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Element, Integer, Reader, Span, Tag, universal};
7
8const FIELD: &str = "tstInfo.accuracy";
9
10/// The optional component selected by the RFC3161 Accuracy schema.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Component {
13    /// Optional universal INTEGER seconds component.
14    Seconds,
15    /// Optional primitive IMPLICIT \[0\] milliseconds component.
16    Millis,
17    /// Optional primitive IMPLICIT \[1\] microseconds component.
18    Micros,
19}
20
21impl Component {
22    const fn index(self) -> usize {
23        match self {
24            Self::Seconds => 0,
25            Self::Millis => 1,
26            Self::Micros => 2,
27        }
28    }
29}
30
31/// Original wire spelling and the common INTEGER decoder's minimal suffix.
32#[derive(Debug, Clone, Copy)]
33pub struct ComponentReading<'a> {
34    element: Element<'a>,
35    value: Integer<'a>,
36}
37impl<'a> ComponentReading<'a> {
38    /// Original whole TLV, distinguishing omitted fields from explicit zero.
39    #[must_use]
40    pub fn element(self) -> Element<'a> {
41        self.element
42    }
43    /// Numeric borrowed view, without another padding scan or a repaired copy.
44    #[must_use]
45    pub fn value(self) -> Integer<'a> {
46        self.value
47    }
48}
49
50/// Why the original component cannot be represented by a nonnegative u64.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum DeviationReason {
53    /// Original two's-complement INTEGER is below zero.
54    Negative,
55    /// Original nonnegative INTEGER exceeds the scalar representation.
56    TooWide,
57}
58
59/// The unrepresentable component remains present, with its original location.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct DeviationError {
62    /// Original component that could not be represented.
63    pub component: Component,
64    /// Whole original component TLV range.
65    pub span: Span,
66    /// No fallback value was substituted for this reason.
67    pub reason: DeviationReason,
68}
69
70/// Readable scalar fields and conformance faults, without token acceptance.
71///
72/// Fixed component storage cannot grow with attacker-controlled field count.
73/// Diagnostics retain their own capacity charge; callers must consider them
74/// before accepting a token/proof. Unknown or out-of-order fields and readable
75/// nonminimal INTEGERs remain faults. Numerically equal duplicates retain the
76/// first original reading; conflicting numeric values refuse rather than selecting
77/// a value. This recovery is an inspection contract, not specified by RFC3161.
78#[derive(Debug)]
79pub struct AccuracyReading<'a> {
80    parts: [Option<ComponentReading<'a>>; 3],
81    faults: OwnedVec<Error>,
82    span: Span,
83}
84
85impl<'a> AccuracyReading<'a> {
86    /// Reads SEQUENCE with optional seconds and primitive IMPLICIT \[0\]/\[1\].
87    ///
88    /// RFC3161 §2.4.2 gives millis/micros the range 1..999 and defaults each
89    /// absent component to zero. Present empty Accuracy is zero, whereas an
90    /// omitted entire Accuracy provides no bound. Readable out-of-range values
91    /// are retained exactly with a fault, never clamped. Resource failures abort.
92    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
93    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
94        if element.tag() != Tag::constructed(universal::SEQUENCE) {
95            return Err(Error::schema(
96                ErrorCode::UnexpectedTag,
97                FIELD,
98                element.span().start,
99            ));
100        }
101        let mut inside = parent
102            .enter(element)
103            .map_err(|error| Error::encoding(error, FIELD))?;
104        let execution = parent.execution();
105        let mut faults = execution.vector();
106        let mut parts: [Option<ComponentReading<'a>>; 3] = [None; 3];
107        let mut last = None;
108        while let Some(field) = inside
109            .next_element()
110            .map_err(|error| Error::encoding(error, FIELD))?
111        {
112            let at = field.span().start;
113            execution
114                .charge_work(1)
115                .map_err(|reason| Error::resource(reason, FIELD, at))?;
116            let component = if field.tag() == Tag::primitive(universal::INTEGER) {
117                Component::Seconds
118            } else if field.tag() == Tag::context(false, 0) {
119                Component::Millis
120            } else if field.tag() == Tag::context(false, 1) {
121                Component::Micros
122            } else {
123                record(
124                    &mut faults,
125                    Error::schema(ErrorCode::UnknownField, FIELD, at),
126                )?;
127                continue;
128            };
129            let index = component.index();
130            if parts[index].is_none() {
131                if last.is_some_and(|last| index < last) {
132                    record(&mut faults, Error::schema(ErrorCode::FieldOrder, FIELD, at))?;
133                }
134                last = Some(last.map_or(index, |last| last.max(index)));
135            }
136            let (value, fault) = inside
137                .integer_reading(&field)
138                .map_err(|error| Error::encoding(error, FIELD))?
139                .into_parts();
140            if let Some(fault) = fault {
141                record(&mut faults, Error::encoding(fault, FIELD))?;
142            }
143            let number = value.as_u64();
144            if number.is_none()
145                || (component != Component::Seconds
146                    && !number.is_some_and(|number| (1..=999).contains(&number)))
147            {
148                record(
149                    &mut faults,
150                    Error::schema(ErrorCode::MalformedValue, FIELD, at),
151                )?;
152            }
153            if let Some(earlier) = parts[index] {
154                // RFC3161 §2.4.2 does not specify duplicate recovery. Our
155                // inspection contract retains one numeric reading and every
156                // fault, including X.690 §8.3.2 redundant-sign violations.
157                // integer_reading already produced minimal borrowed suffixes;
158                // compare them without a bounded numeric projection/sentinel.
159                // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
160                if earlier.value.as_bytes().len() != value.as_bytes().len() {
161                    return Err(Error::schema(ErrorCode::DuplicateField, FIELD, at));
162                }
163                execution
164                    .charge_work(value.as_bytes().len() as u64)
165                    .map_err(|reason| Error::resource(reason, FIELD, at))?;
166                if earlier.value.as_bytes() != value.as_bytes() {
167                    return Err(Error::schema(ErrorCode::DuplicateField, FIELD, at));
168                }
169                record(
170                    &mut faults,
171                    Error::schema(ErrorCode::DuplicateField, FIELD, at),
172                )?;
173                continue;
174            }
175            parts[index] = Some(ComponentReading {
176                element: field,
177                value,
178            });
179        }
180        Ok(Self {
181            parts,
182            faults: faults.into_owned(),
183            span: element.span(),
184        })
185    }
186
187    /// Original component, absent only when no such field was written.
188    #[must_use]
189    pub fn component(&self, component: Component) -> Option<ComponentReading<'a>> {
190        self.parts[component.index()]
191    }
192    /// Original complete Accuracy encoding range.
193    #[must_use]
194    pub fn span(&self) -> Span {
195        self.span
196    }
197    /// Original schema/conformance faults; accessing them performs no new scan.
198    #[must_use]
199    pub fn faults(&self) -> &[Error] {
200        &self.faults
201    }
202    /// Move diagnostic storage without cloning paths or refunding live capacity.
203    #[must_use]
204    pub fn into_faults(self) -> OwnedVec<Error> {
205        self.faults
206    }
207
208    /// Exact nonnegative deviation, independently of conformance acceptance.
209    /// Negative/wide components are typed failures, never zero or `u64::MAX`.
210    pub fn deviation(&self) -> core::result::Result<Accuracy, DeviationError> {
211        let mut values = [0; 3];
212        for component in [Component::Seconds, Component::Millis, Component::Micros] {
213            let index = component.index();
214            if let Some(part) = self.parts[index] {
215                values[index] = part.value.as_u64().ok_or_else(|| DeviationError {
216                    component,
217                    span: part.element.span(),
218                    reason: if part.value.is_negative() {
219                        DeviationReason::Negative
220                    } else {
221                        DeviationReason::TooWide
222                    },
223                })?;
224            }
225        }
226        Ok(Accuracy::new(values[0], values[1], values[2]))
227    }
228}