Skip to main content

oxirush_aper/
oid.rs

1//! OBJECT IDENTIFIER values.
2
3use alloc::format;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::fmt;
7
8use crate::{Error, ErrorKind};
9
10/// An OBJECT IDENTIFIER: its arcs.
11///
12/// ```
13/// use oxirush_aper::ObjectIdentifier;
14///
15/// let identifier = ObjectIdentifier::new(vec![0, 4, 0, 1989, 0])?;
16/// assert_eq!(identifier.arcs(), [0, 4, 0, 1989, 0]);
17/// assert_eq!(identifier.to_string(), "0.4.0.1989.0");
18///
19/// // The first arc is 0, 1 or 2, and the second is below 40 under 0 and 1
20/// assert!(ObjectIdentifier::new(vec![3, 1]).is_err());
21/// assert!(ObjectIdentifier::new(vec![1, 40]).is_err());
22/// # Ok::<(), oxirush_aper::Error>(())
23/// ```
24#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub struct ObjectIdentifier(Vec<u32>);
26
27impl ObjectIdentifier {
28    /// The object identifier of these arcs.
29    ///
30    /// # Errors
31    ///
32    /// An error of the kind `Argument` for fewer than two arcs, a first arc above 2,
33    /// and a second arc that the first one does not have (ITU-T X.660 (07/2011)
34    /// §A.2): 40 or more under 0 and 1.
35    pub fn new(arcs: Vec<u32>) -> Result<Self, Error> {
36        let argument = |what: String| Error::new(ErrorKind::Argument, what);
37        match arcs[..] {
38            [] | [_] => Err(argument(format!(
39                "an object identifier has two arcs at least, not {}",
40                arcs.len()
41            ))),
42            [first, ..] if first > 2 => Err(argument(format!(
43                "the first arc of an object identifier is 0, 1 or 2, not {first}"
44            ))),
45            [first, second, ..] if first < 2 && second >= 40 => Err(argument(format!(
46                "the second arc of an object identifier is below 40 under {first}, not {second}"
47            ))),
48            [2, second, ..] if second > u32::MAX - 80 => Err(argument(format!(
49                "the second arc of an object identifier, {second}, is more than this crate holds"
50            ))),
51            _ => Ok(Self(arcs)),
52        }
53    }
54
55    /// The arcs.
56    pub fn arcs(&self) -> &[u32] {
57        &self.0
58    }
59
60    /// The contents octets that the Basic Encoding Rules give the identifier (ITU-T
61    /// X.690 (02/2021) §8.19): the first two arcs in one number, then each arc, seven
62    /// bits to an octet, the most significant first, with a bit that says that another
63    /// octet follows.
64    pub(crate) fn to_contents(&self) -> Vec<u8> {
65        let mut contents = Vec::with_capacity(self.0.len() + 2);
66        let first = u64::from(self.0[0]) * 40 + u64::from(self.0[1]);
67        let rest = self.0[2..].iter().map(|arc| u64::from(*arc));
68        for number in core::iter::once(first).chain(rest) {
69            let septets = (64 - number.leading_zeros() as usize).div_ceil(7).max(1);
70            for septet in (0..septets).rev() {
71                let more = if septet > 0 { 0x80 } else { 0x00 };
72                contents.push((number >> (septet * 7)) as u8 & 0x7f | more);
73            }
74        }
75        contents
76    }
77
78    /// The identifier of contents octets.
79    pub(crate) fn from_contents(contents: &[u8]) -> Result<Self, Error> {
80        let mut arcs = Vec::new();
81        let mut number: u64 = 0;
82        let mut started = false;
83        for octet in contents {
84            // A writer gives a number as few octets as hold it (X.690 §8.19.2). One that
85            // starts with octets that hold nothing is read as the number that it is.
86            started = true;
87            number = number << 7 | u64::from(octet & 0x7f);
88            if number > u64::from(u32::MAX) {
89                return Err(Error::decode(
90                    "an arc of an object identifier is more than 32 bits",
91                ));
92            }
93            if octet & 0x80 == 0 {
94                match arcs.is_empty() {
95                    true => {
96                        let first = (number / 40).min(2);
97                        arcs.push(first as u32);
98                        arcs.push((number - first * 40) as u32);
99                    }
100                    false => arcs.push(number as u32),
101                }
102                (number, started) = (0, false);
103            }
104        }
105        if started || arcs.is_empty() {
106            return Err(Error::decode(
107                "the octets of an object identifier end before its last arc does",
108            ));
109        }
110        Ok(Self(arcs))
111    }
112}
113
114/// The arcs in decimal, with a full stop between them: `0.4.0.1989.0`.
115impl fmt::Display for ObjectIdentifier {
116    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117        for (at, arc) in self.0.iter().enumerate() {
118            if at > 0 {
119                formatter.write_str(".")?;
120            }
121            write!(formatter, "{arc}")?;
122        }
123        Ok(())
124    }
125}
126
127/// The identifier that `text` writes: its arcs in decimal, with a full stop between
128/// them.
129impl core::str::FromStr for ObjectIdentifier {
130    type Err = Error;
131
132    fn from_str(text: &str) -> Result<Self, Error> {
133        let arcs: Result<Vec<u32>, _> = text.split('.').map(str::parse).collect();
134        match arcs {
135            Ok(arcs) => Self::new(arcs),
136            Err(_) => Err(Error::new(
137                ErrorKind::Argument,
138                format!(
139                    "{text:?} is no object identifier: its arcs are written in decimal, with a \
140                     full stop between them"
141                ),
142            )),
143        }
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use alloc::string::ToString;
151    use alloc::vec;
152
153    #[test]
154    fn an_object_identifier_is_its_arcs_and_the_contents_octets_of_ber() {
155        // ITU-T X.690 (02/2021) §8.19.5: {joint-iso-itu-t 100 3} is 0x813403.
156        let example = ObjectIdentifier::new(vec![2, 100, 3]).unwrap();
157        assert_eq!(example.to_contents(), [0x81, 0x34, 0x03]);
158        assert_eq!(
159            ObjectIdentifier::from_contents(&[0x81, 0x34, 0x03]).unwrap(),
160            example
161        );
162        assert_eq!(example.to_string(), "2.100.3");
163        assert_eq!("2.100.3".parse::<ObjectIdentifier>().unwrap(), example);
164
165        for arcs in [
166            vec![0, 0],
167            vec![0, 39],
168            vec![1, 39, 0],
169            vec![2, 0],
170            vec![2, 999, u32::MAX, 0, 127, 128, 16383, 16384],
171            vec![0, 4, 0, 1989, 0],
172        ] {
173            let identifier = ObjectIdentifier::new(arcs.clone()).unwrap();
174            let contents = identifier.to_contents();
175            let read = ObjectIdentifier::from_contents(&contents).unwrap();
176            assert_eq!(read.arcs(), arcs);
177        }
178
179        for arcs in [vec![], vec![1], vec![3, 0], vec![0, 40], vec![1, 40]] {
180            let error = ObjectIdentifier::new(arcs).unwrap_err();
181            assert_eq!(error.kind(), ErrorKind::Argument);
182        }
183        assert!("1.2.x".parse::<ObjectIdentifier>().is_err());
184        assert!("".parse::<ObjectIdentifier>().is_err());
185
186        // An arc that starts with an octet that holds nothing is the number that it is.
187        let padded = ObjectIdentifier::from_contents(&[0x2a, 0x80, 0x01]).unwrap();
188        assert_eq!(padded.arcs(), [1, 2, 1]);
189        // No arc, an arc that does not end, and one of more than 32 bits.
190        for contents in [&[][..], &[0x81], &[0x2a, 0x90, 0x80, 0x80, 0x80, 0x00]] {
191            let error = ObjectIdentifier::from_contents(contents).unwrap_err();
192            assert_eq!(error.kind(), ErrorKind::Decode, "{contents:02x?}");
193        }
194    }
195}