/rust/registry/src/index.crates.io-1949cf8c6b5b557f/const-oid-0.9.6/src/parser.rs
Line | Count | Source |
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` |
19 | 0 | pub(crate) const fn parse(s: &str) -> Result<Self> { |
20 | 0 | let bytes = s.as_bytes(); |
21 | | |
22 | 0 | if bytes.is_empty() { |
23 | 0 | return Err(Error::Empty); |
24 | 0 | } |
25 | | |
26 | 0 | match bytes[0] { |
27 | 0 | b'0'..=b'9' => Self { |
28 | 0 | current_arc: 0, |
29 | 0 | encoder: Encoder::new(), |
30 | 0 | } |
31 | 0 | .parse_bytes(bytes), |
32 | 0 | actual => Err(Error::DigitExpected { actual }), |
33 | | } |
34 | 0 | } |
35 | | |
36 | | /// Finish parsing, returning the result |
37 | 0 | pub(crate) const fn finish(self) -> Result<ObjectIdentifier> { |
38 | 0 | self.encoder.finish() |
39 | 0 | } |
40 | | |
41 | | /// Parse the remaining bytes |
42 | 0 | const fn parse_bytes(mut self, bytes: &[u8]) -> Result<Self> { |
43 | 0 | match bytes { |
44 | | // TODO(tarcieri): use `?` when stable in `const fn` |
45 | 0 | [] => match self.encoder.arc(self.current_arc) { |
46 | 0 | Ok(encoder) => { |
47 | 0 | self.encoder = encoder; |
48 | 0 | Ok(self) |
49 | | } |
50 | 0 | Err(err) => Err(err), |
51 | | }, |
52 | | // TODO(tarcieri): checked arithmetic |
53 | | #[allow(clippy::integer_arithmetic)] |
54 | 0 | [byte @ b'0'..=b'9', remaining @ ..] => { |
55 | 0 | let digit = byte.saturating_sub(b'0'); |
56 | 0 | self.current_arc = self.current_arc * 10 + digit as Arc; |
57 | 0 | self.parse_bytes(remaining) |
58 | | } |
59 | 0 | [b'.', remaining @ ..] => { |
60 | 0 | if remaining.is_empty() { |
61 | 0 | return Err(Error::TrailingDot); |
62 | 0 | } |
63 | | |
64 | | // TODO(tarcieri): use `?` when stable in `const fn` |
65 | 0 | match self.encoder.arc(self.current_arc) { |
66 | 0 | Ok(encoder) => { |
67 | 0 | self.encoder = encoder; |
68 | 0 | self.current_arc = 0; |
69 | 0 | self.parse_bytes(remaining) |
70 | | } |
71 | 0 | Err(err) => Err(err), |
72 | | } |
73 | | } |
74 | 0 | [byte, ..] => Err(Error::DigitExpected { actual: *byte }), |
75 | | } |
76 | 0 | } |
77 | | } |
78 | | |
79 | | #[cfg(test)] |
80 | | mod tests { |
81 | | use super::Parser; |
82 | | use crate::Error; |
83 | | |
84 | | #[test] |
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] |
91 | | fn reject_empty_string() { |
92 | | assert_eq!(Parser::parse("").err().unwrap(), Error::Empty); |
93 | | } |
94 | | |
95 | | #[test] |
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] |
109 | | fn reject_trailing_dot() { |
110 | | assert_eq!(Parser::parse("1.23.").err().unwrap(), Error::TrailingDot); |
111 | | } |
112 | | } |