• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! OID string parser with `const` support.
2 
3 use crate::{encoder::Encoder, Arc, Error, ObjectIdentifier, Result};
4 
5 /// Const-friendly OID string parser.
6 ///
7 /// Parses an OID from the dotted string representation.
8 #[derive(Debug)]
9 pub(crate) struct Parser {
10     /// Current arc in progress
11     current_arc: Arc,
12 
13     /// BER/DER encoder
14     encoder: Encoder,
15 }
16 
17 impl Parser {
18     /// Parse an OID from a dot-delimited string e.g. `1.2.840.113549.1.1.1`
parse(s: &str) -> Result<Self>19     pub(crate) const fn parse(s: &str) -> Result<Self> {
20         let bytes = s.as_bytes();
21 
22         if bytes.is_empty() {
23             return Err(Error::Empty);
24         }
25 
26         match bytes[0] {
27             b'0'..=b'9' => Self {
28                 current_arc: 0,
29                 encoder: Encoder::new(),
30             }
31             .parse_bytes(bytes),
32             actual => Err(Error::DigitExpected { actual }),
33         }
34     }
35 
36     /// Finish parsing, returning the result
finish(self) -> Result<ObjectIdentifier>37     pub(crate) const fn finish(self) -> Result<ObjectIdentifier> {
38         self.encoder.finish()
39     }
40 
41     /// Parse the remaining bytes
parse_bytes(mut self, bytes: &[u8]) -> Result<Self>42     const fn parse_bytes(mut self, bytes: &[u8]) -> Result<Self> {
43         match bytes {
44             // TODO(tarcieri): use `?` when stable in `const fn`
45             [] => match self.encoder.arc(self.current_arc) {
46                 Ok(encoder) => {
47                     self.encoder = encoder;
48                     Ok(self)
49                 }
50                 Err(err) => Err(err),
51             },
52             // TODO(tarcieri): checked arithmetic
53             #[allow(clippy::integer_arithmetic)]
54             [byte @ b'0'..=b'9', remaining @ ..] => {
55                 let digit = byte.saturating_sub(b'0');
56                 self.current_arc = self.current_arc * 10 + digit as Arc;
57                 self.parse_bytes(remaining)
58             }
59             [b'.', remaining @ ..] => {
60                 if remaining.is_empty() {
61                     return Err(Error::TrailingDot);
62                 }
63 
64                 // TODO(tarcieri): use `?` when stable in `const fn`
65                 match self.encoder.arc(self.current_arc) {
66                     Ok(encoder) => {
67                         self.encoder = encoder;
68                         self.current_arc = 0;
69                         self.parse_bytes(remaining)
70                     }
71                     Err(err) => Err(err),
72                 }
73             }
74             [byte, ..] => Err(Error::DigitExpected { actual: *byte }),
75         }
76     }
77 }
78 
79 #[cfg(test)]
80 mod tests {
81     use super::Parser;
82     use crate::Error;
83 
84     #[test]
parse()85     fn parse() {
86         let oid = Parser::parse("1.23.456").unwrap().finish().unwrap();
87         assert_eq!(oid, "1.23.456".parse().unwrap());
88     }
89 
90     #[test]
reject_empty_string()91     fn reject_empty_string() {
92         assert_eq!(Parser::parse("").err().unwrap(), Error::Empty);
93     }
94 
95     #[test]
reject_non_digits()96     fn reject_non_digits() {
97         assert_eq!(
98             Parser::parse("X").err().unwrap(),
99             Error::DigitExpected { actual: b'X' }
100         );
101 
102         assert_eq!(
103             Parser::parse("1.2.X").err().unwrap(),
104             Error::DigitExpected { actual: b'X' }
105         );
106     }
107 
108     #[test]
reject_trailing_dot()109     fn reject_trailing_dot() {
110         assert_eq!(Parser::parse("1.23.").err().unwrap(), Error::TrailingDot);
111     }
112 }
113