diff options
Diffstat (limited to 'vendor/const-oid/src/parser.rs')
-rw-r--r-- | vendor/const-oid/src/parser.rs | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/vendor/const-oid/src/parser.rs b/vendor/const-oid/src/parser.rs new file mode 100644 index 000000000..6f875faaa --- /dev/null +++ b/vendor/const-oid/src/parser.rs @@ -0,0 +1,112 @@ +//! OID string parser with `const` support. + +use crate::{encoder::Encoder, Arc, Error, ObjectIdentifier, Result}; + +/// Const-friendly OID string parser. +/// +/// Parses an OID from the dotted string representation. +#[derive(Debug)] +pub(crate) struct Parser { + /// Current arc in progress + current_arc: Arc, + + /// BER/DER encoder + encoder: Encoder, +} + +impl Parser { + /// Parse an OID from a dot-delimited string e.g. `1.2.840.113549.1.1.1` + pub(crate) const fn parse(s: &str) -> Result<Self> { + let bytes = s.as_bytes(); + + if bytes.is_empty() { + return Err(Error::Empty); + } + + match bytes[0] { + b'0'..=b'9' => Self { + current_arc: 0, + encoder: Encoder::new(), + } + .parse_bytes(bytes), + actual => Err(Error::DigitExpected { actual }), + } + } + + /// Finish parsing, returning the result + pub(crate) const fn finish(self) -> Result<ObjectIdentifier> { + self.encoder.finish() + } + + /// Parse the remaining bytes + const fn parse_bytes(mut self, bytes: &[u8]) -> Result<Self> { + match bytes { + // TODO(tarcieri): use `?` when stable in `const fn` + [] => match self.encoder.arc(self.current_arc) { + Ok(encoder) => { + self.encoder = encoder; + Ok(self) + } + Err(err) => Err(err), + }, + // TODO(tarcieri): checked arithmetic + #[allow(clippy::integer_arithmetic)] + [byte @ b'0'..=b'9', remaining @ ..] => { + let digit = byte.saturating_sub(b'0'); + self.current_arc = self.current_arc * 10 + digit as Arc; + self.parse_bytes(remaining) + } + [b'.', remaining @ ..] => { + if remaining.is_empty() { + return Err(Error::TrailingDot); + } + + // TODO(tarcieri): use `?` when stable in `const fn` + match self.encoder.arc(self.current_arc) { + Ok(encoder) => { + self.encoder = encoder; + self.current_arc = 0; + self.parse_bytes(remaining) + } + Err(err) => Err(err), + } + } + [byte, ..] => Err(Error::DigitExpected { actual: *byte }), + } + } +} + +#[cfg(test)] +mod tests { + use super::Parser; + use crate::Error; + + #[test] + fn parse() { + let oid = Parser::parse("1.23.456").unwrap().finish().unwrap(); + assert_eq!(oid, "1.23.456".parse().unwrap()); + } + + #[test] + fn reject_empty_string() { + assert_eq!(Parser::parse("").err().unwrap(), Error::Empty); + } + + #[test] + fn reject_non_digits() { + assert_eq!( + Parser::parse("X").err().unwrap(), + Error::DigitExpected { actual: b'X' } + ); + + assert_eq!( + Parser::parse("1.2.X").err().unwrap(), + Error::DigitExpected { actual: b'X' } + ); + } + + #[test] + fn reject_trailing_dot() { + assert_eq!(Parser::parse("1.23.").err().unwrap(), Error::TrailingDot); + } +} |