1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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);
}
}
|