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