Skip to main content

structured_pkix/temporal/
precision.rs

1//! Arbitrary decimal precision without inventing an exact nanosecond.
2use super::{LeapTable, Reading, Uncertainty};
3use crate::error::{Error, Result};
4use structured_asn1::memory::{MemoryError, OwnedVec, Work};
5
6/// Original generalized-time value with every significant subnanosecond digit.
7/// Its retained capacity is independent of the source parser. No accuracy,
8/// trusted clock or successful timestamp proof is implied.
9#[derive(Debug)]
10pub struct ExactReading {
11    pub(super) reading: Reading,
12    tail: Option<OwnedVec<u8>>,
13}
14/// Comparison refuses unavailable UTC facts separately from resource limits.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CompareError {
17    /// Admission failed before comparison/UTC lookup.
18    Resource(MemoryError),
19    /// The supplied UTC correction facts do not qualify an original label.
20    Uncertain(Uncertainty),
21}
22impl ExactReading {
23    pub(super) fn new<W: Work>(
24        reading: Reading,
25        fraction: Option<(&[u8], i128)>,
26        work: &W,
27        field: &'static str,
28        at: u64,
29    ) -> Result<Self> {
30        if !reading.has_subnanosecond_remainder() {
31            return Ok(Self {
32                reading,
33                tail: None,
34            });
35        }
36        let (mut digits, unit) =
37            fraction.expect("nonzero fractional remainder came from a fraction");
38        let failure = |cause| Error::resource(cause, field, at);
39        work.charge_work(
40            (digits.len() as u64)
41                .checked_mul(3)
42                .ok_or_else(|| failure(MemoryError::WorkExceeded))?,
43        )
44        .map_err(failure)?;
45        // All grammar units are whole seconds/minutes/hours. Their 10^9
46        // factor means only digits beyond nine can contribute to a fractional
47        // nanosecond. Do not allocate the already represented integer prefix.
48        while digits.last() == Some(&b'0') {
49            digits = &digits[..digits.len() - 1];
50        }
51        debug_assert!(digits.len() > 9 && unit % 1_000_000_000 == 0);
52        let digits = &digits[9..];
53        let unit = unit / 1_000_000_000;
54        let mut tail = work.vector();
55        tail.try_reserve_exact(digits.len()).map_err(failure)?;
56        let mut carry = 0i128;
57        for digit in digits.iter().rev() {
58            // Same bounded reverse multiplication as the parser: unit <= an
59            // hour in nanoseconds, carry < unit; no big integer or truncation.
60            let product = i128::from(digit - b'0') * unit + carry;
61            tail.try_push(u8::try_from(product % 10).expect("decimal remainder") + b'0')
62                .map_err(failure)?;
63            carry = product / 10;
64        }
65        tail.reverse();
66        while tail.last() == Some(&b'0') {
67            tail.pop();
68        }
69        debug_assert!(!tail.is_empty());
70        Ok(Self {
71            reading,
72            tail: Some(tail.into_owned()),
73        })
74    }
75    /// The nanosecond floor and whether an exact finer remainder exists.
76    #[must_use]
77    pub fn reading(&self) -> Reading {
78        self.reading
79    }
80    /// Copies only retained fractional precision under the current operation.
81    pub fn try_copy_in<W: Work>(&self, work: &W, field: &'static str, at: u64) -> Result<Self> {
82        let tail = if let Some(tail) = &self.tail {
83            let mut copy = work.vector();
84            copy.try_extend_from_slice(tail)
85                .map_err(|cause| Error::resource(cause, field, at))?;
86            Some(copy.into_owned())
87        } else {
88            None
89        };
90        Ok(Self {
91            reading: self.reading,
92            tail,
93        })
94    }
95    /// Full UTC ordering with virtual decimal zero padding (X.680 ยง46).
96    /// Both source labels and all possible comparison work are admitted first.
97    pub fn compare<W: Work>(
98        &self,
99        other: &Self,
100        utc: &LeapTable<'_>,
101        work: &W,
102    ) -> core::result::Result<core::cmp::Ordering, CompareError> {
103        let bits = usize::BITS - utc.transitions().len().leading_zeros();
104        work.charge_work(32 + 6 * (u64::from(bits) + 1))
105            .map_err(CompareError::Resource)?;
106        let left = utc
107            .shift(self.reading.start(), 0)
108            .map_err(CompareError::Uncertain)?;
109        let right = utc
110            .shift(other.reading.start(), 0)
111            .map_err(CompareError::Uncertain)?;
112        if left != right {
113            return Ok(left.cmp(&right));
114        }
115        let a = self.tail.as_deref().unwrap_or(&[]);
116        let b = other.tail.as_deref().unwrap_or(&[]);
117        work.charge_work(1 + a.len().max(b.len()) as u64)
118            .map_err(CompareError::Resource)?;
119        for index in 0..a.len().max(b.len()) {
120            // A missing decimal digit is numeric zero, not an input fallback.
121            let left = a.get(index).copied().unwrap_or(b'0');
122            let right = b.get(index).copied().unwrap_or(b'0');
123            if left != right {
124                return Ok(left.cmp(&right));
125            }
126        }
127        Ok(core::cmp::Ordering::Equal)
128    }
129}