Coverage Report

Created: 2026-08-13 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/const-oid-0.10.2/src/arcs.rs
Line
Count
Source
1
//! Arcs are integer values which exist within an OID's hierarchy.
2
3
use crate::{Error, Result};
4
5
#[cfg(doc)]
6
use crate::ObjectIdentifier;
7
8
/// Type alias used to represent an "arc", i.e. integer identifier value, where an OID comprises a
9
/// sequence of arcs.
10
///
11
/// X.660 does not define a maximum size of an arc. We instead follow Mozilla* conventions for
12
/// maximum values of an arc, with a maximum value of 2^32-1 (4294967295), a.k.a. [`u32::MAX`]
13
/// with [`Arc`] being a type alias for [`u32`].
14
///
15
/// Note that this means we deliberately do *NOT* support UUIDs used as OIDs.
16
///
17
/// *NOTE: please see this study for a survey of how various OID libraries handle maximum arcs:
18
/// <https://misc.daniel-marschall.de/asn.1/oid_facts.html>
19
pub type Arc = u32;
20
21
/// Maximum value of the first arc in an OID.
22
pub(crate) const ARC_MAX_FIRST: Arc = 2;
23
24
/// Maximum value of the second arc in an OID.
25
pub(crate) const ARC_MAX_SECOND: Arc = 39;
26
27
/// Maximum number of bytes supported in an arc.
28
///
29
/// Note that OIDs are base 128 encoded (with continuation bits), so we must consider how many bytes
30
/// are required when each byte can only represent 7-bits of the input.
31
const ARC_MAX_BYTES: usize = (Arc::BITS as usize).div_ceil(7);
32
33
/// Maximum value of the last byte in an arc.
34
const ARC_MAX_LAST_OCTET: u8 = 0b11110000; // Max bytes of leading 1-bits
35
36
/// [`Iterator`] over [`Arc`] values (a.k.a. nodes) in an [`ObjectIdentifier`].
37
///
38
/// This iterates over all arcs in an OID, including the root.
39
pub struct Arcs<'a> {
40
    /// OID bytes we're iterating over.
41
    bytes: &'a [u8],
42
43
    /// Current position within the serialized BER bytes of this OID.
44
    cursor: Option<usize>,
45
}
46
47
impl<'a> Arcs<'a> {
48
    /// Create a new iterator over an OID encoded as BER bytes.
49
0
    pub(crate) fn new(bytes: &'a [u8]) -> Self {
50
0
        Self {
51
0
            bytes,
52
0
            cursor: None,
53
0
        }
54
0
    }
55
56
    /// Try to parse the next arc in this OID.
57
    ///
58
    /// This method is fallible so it can be used as a first pass to determine
59
    /// that the arcs in the OID are well-formed.
60
0
    pub(crate) fn try_next(&mut self) -> Result<Option<Arc>> {
61
0
        match self.cursor {
62
            // Indicates we're on the root arc
63
            None => {
64
0
                let root_byte = *self.bytes.first().ok_or(Error::Empty)?;
65
0
                let root = RootArcs::try_from(root_byte)?;
66
0
                self.cursor = Some(0);
67
0
                Ok(Some(root.first_arc()))
68
            }
69
            Some(0) => {
70
0
                let root = RootArcs::try_from(self.bytes[0])?;
71
0
                self.cursor = Some(1);
72
0
                Ok(Some(root.second_arc()))
73
            }
74
0
            Some(offset) => {
75
0
                let mut result = 0;
76
0
                let mut arc_bytes = 0;
77
78
                loop {
79
0
                    let len = checked_add!(offset, arc_bytes);
80
81
0
                    match self.bytes.get(len).cloned() {
82
                        // The arithmetic below includes advance checks
83
                        // against `ARC_MAX_BYTES` and `ARC_MAX_LAST_OCTET`
84
                        // which ensure the operations will not overflow.
85
                        #[allow(clippy::arithmetic_side_effects)]
86
0
                        Some(byte) => {
87
0
                            arc_bytes = checked_add!(arc_bytes, 1);
88
89
0
                            if (arc_bytes > ARC_MAX_BYTES) && (byte & ARC_MAX_LAST_OCTET != 0) {
90
0
                                return Err(Error::ArcTooBig);
91
0
                            }
92
93
0
                            result = (result << 7) | (byte & 0b1111111) as Arc;
94
95
0
                            if byte & 0b10000000 == 0 {
96
0
                                self.cursor = Some(checked_add!(offset, arc_bytes));
97
0
                                return Ok(Some(result));
98
0
                            }
99
                        }
100
                        None => {
101
0
                            if arc_bytes == 0 {
102
0
                                return Ok(None);
103
                            } else {
104
0
                                return Err(Error::Base128);
105
                            }
106
                        }
107
                    }
108
                }
109
            }
110
        }
111
0
    }
112
}
113
114
impl Iterator for Arcs<'_> {
115
    type Item = Arc;
116
117
0
    fn next(&mut self) -> Option<Arc> {
118
        // ObjectIdentifier constructors should ensure the OID is well-formed
119
0
        self.try_next().expect("OID malformed")
120
0
    }
121
}
122
123
/// Byte containing the first and second arcs of an OID.
124
///
125
/// This is represented this way in order to reduce the overall size of the
126
/// [`ObjectIdentifier`] struct.
127
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
128
struct RootArcs(u8);
129
130
impl RootArcs {
131
    /// Create [`RootArcs`] from the first and second arc values represented
132
    /// as `Arc` integers.
133
0
    pub(crate) const fn new(first_arc: Arc, second_arc: Arc) -> Result<Self> {
134
0
        if first_arc > ARC_MAX_FIRST {
135
0
            return Err(Error::ArcInvalid { arc: first_arc });
136
0
        }
137
138
0
        if second_arc > ARC_MAX_SECOND {
139
0
            return Err(Error::ArcInvalid { arc: second_arc });
140
0
        }
141
142
        // The checks above ensure this operation will not overflow
143
        #[allow(clippy::arithmetic_side_effects)]
144
0
        let byte = (first_arc * (ARC_MAX_SECOND + 1)) as u8 + second_arc as u8;
145
146
0
        Ok(Self(byte))
147
0
    }
148
149
    /// Get the value of the first arc
150
    #[allow(clippy::arithmetic_side_effects)]
151
0
    pub(crate) const fn first_arc(self) -> Arc {
152
0
        self.0 as Arc / (ARC_MAX_SECOND + 1)
153
0
    }
154
155
    /// Get the value of the second arc
156
    #[allow(clippy::arithmetic_side_effects)]
157
0
    pub(crate) const fn second_arc(self) -> Arc {
158
0
        self.0 as Arc % (ARC_MAX_SECOND + 1)
159
0
    }
160
}
161
162
impl TryFrom<u8> for RootArcs {
163
    type Error = Error;
164
165
    // Ensured not to overflow by constructor invariants
166
    #[allow(clippy::arithmetic_side_effects)]
167
0
    fn try_from(octet: u8) -> Result<Self> {
168
0
        let first = octet as Arc / (ARC_MAX_SECOND + 1);
169
0
        let second = octet as Arc % (ARC_MAX_SECOND + 1);
170
0
        let result = Self::new(first, second)?;
171
0
        debug_assert_eq!(octet, result.0);
172
0
        Ok(result)
173
0
    }
174
}
175
176
impl From<RootArcs> for u8 {
177
0
    fn from(root_arcs: RootArcs) -> u8 {
178
0
        root_arcs.0
179
0
    }
180
}