Skip to content

Commit

Permalink
const-oid: fallible const parser + ::new_unwrap
Browse files Browse the repository at this point in the history
With the introduction of const panic it's now possible to implement a
proper fallible parser with `const fn`, eliminating some of the
duplication between the `const fn` and fallible code in this crate.

Unfortunately it's not yet possible to call `Result::unwrap` in a const
context, so instead this commit adds an `ObjectIdentifier::new_unwrap`
method which leverages const panic to print the `Result` as best it can.
In future versions of Rust, it will be possible to replace this with
`ObjectIdentifier::new(...).unwrap()`.
  • Loading branch information
tarcieri committed Mar 2, 2022
1 parent cae980c commit 8d33af4
Show file tree
Hide file tree
Showing 10 changed files with 1,143 additions and 1,070 deletions.
2 changes: 1 addition & 1 deletion const-oid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ This library supports parsing OIDs in const contexts, e.g.:
```rust
use const_oid::ObjectIdentifier;

pub const MY_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1");
pub const MY_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
```

The OID parser is implemented entirely in terms of `const fn` and without the
Expand Down
2 changes: 1 addition & 1 deletion const-oid/oiddbgen/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Node {

quote! {
pub const #symb: crate::NamedOid<'_> = crate::NamedOid {
oid: crate::ObjectIdentifier::new(#obid),
oid: crate::ObjectIdentifier::new_unwrap(#obid),
name: #name,
};
}
Expand Down
14 changes: 9 additions & 5 deletions const-oid/src/arcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,26 @@ pub(crate) struct RootArcs(u8);
impl RootArcs {
/// Create [`RootArcs`] from the first and second arc values represented
/// as `Arc` integers.
pub(crate) fn new(first_arc: Arc, second_arc: Arc) -> Result<Self> {
if first_arc > ARC_MAX_FIRST || second_arc > ARC_MAX_SECOND {
return Err(Error);
pub(crate) const fn new(first_arc: Arc, second_arc: Arc) -> Result<Self> {
if first_arc > ARC_MAX_FIRST {
return Err(Error::ArcInvalid { arc: first_arc });
}

if second_arc > ARC_MAX_SECOND {
return Err(Error::ArcInvalid { arc: second_arc });
}

let byte = (first_arc * (ARC_MAX_SECOND + 1)) as u8 + second_arc as u8;
Ok(Self(byte))
}

/// Get the value of the first arc
pub(crate) fn first_arc(self) -> Arc {
pub(crate) const fn first_arc(self) -> Arc {
self.0 as Arc / (ARC_MAX_SECOND + 1)
}

/// Get the value of the second arc
pub(crate) fn second_arc(self) -> Arc {
pub(crate) const fn second_arc(self) -> Arc {
self.0 as Arc % (ARC_MAX_SECOND + 1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion const-oid/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ mod tests {
let cn = super::DB.by_oid(&CN.oid).expect("cn not found");
assert_eq!(&CN, cn);

let none = ObjectIdentifier::new("0.1.2.3.4.5.6.7.8.9");
let none = ObjectIdentifier::new_unwrap("0.1.2.3.4.5.6.7.8.9");
assert_eq!(None, super::DB.by_oid(&none));
}

Expand Down
1,862 changes: 931 additions & 931 deletions const-oid/src/db/gen.rs

Large diffs are not rendered by default.

75 changes: 47 additions & 28 deletions const-oid/src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
};

/// BER/DER encoder
#[derive(Debug)]
pub(crate) struct Encoder {
/// Current state
state: State,
Expand All @@ -18,6 +19,7 @@ pub(crate) struct Encoder {
}

/// Current state of the encoder
#[derive(Debug)]
enum State {
/// Initial state - no arcs yet encoded
Initial,
Expand All @@ -40,59 +42,76 @@ impl Encoder {
}

/// Encode an [`Arc`] as base 128 into the internal buffer
pub(crate) const fn encode(mut self, arc: Arc) -> Self {
pub(crate) const fn encode(mut self, arc: Arc) -> Result<Self> {
match self.state {
State::Initial => {
assert!(arc <= ARC_MAX_FIRST, "invalid first arc (must be 0-2)");
if arc > ARC_MAX_FIRST {
return Err(Error::ArcInvalid { arc });
}

self.state = State::FirstArc(arc);
self
Ok(self)
}
State::FirstArc(first_arc) => {
assert!(arc <= ARC_MAX_SECOND, "invalid second arc (must be 0-39)");
if arc > ARC_MAX_SECOND {
return Err(Error::ArcInvalid { arc });
}

self.state = State::Body;
self.bytes[0] = (first_arc * (ARC_MAX_SECOND + 1)) as u8 + arc as u8;
self.cursor = 1;
self
Ok(self)
}
State::Body => {
// Total number of bytes in encoded arc - 1
let nbytes = base128_len(arc);

assert!(
self.cursor + nbytes + 1 < ObjectIdentifier::MAX_SIZE,
"OID too long (exceeded max DER bytes)"
);
if self.cursor + nbytes + 1 >= ObjectIdentifier::MAX_SIZE {
return Err(Error::Length);
}

let new_cursor = self.cursor + nbytes + 1;
let mut result = self.encode_base128_byte(arc, nbytes, false);
result.cursor = new_cursor;
result

// TODO(tarcieri): use `?` when stable in `const fn`
match self.encode_base128_byte(arc, nbytes, false) {
Ok(mut encoder) => {
encoder.cursor = new_cursor;
Ok(encoder)
}
Err(err) => Err(err),
}
}
}
}

/// Finish encoding an OID
pub(crate) const fn finish(self) -> ObjectIdentifier {
assert!(self.cursor >= 2, "OID too short (minimum 3 arcs)");
ObjectIdentifier {
bytes: self.bytes,
length: self.cursor as u8,
pub(crate) const fn finish(self) -> Result<ObjectIdentifier> {
if self.cursor >= 2 {
Ok(ObjectIdentifier {
bytes: self.bytes,
length: self.cursor as u8,
})
} else {
Err(Error::NotEnoughArcs)
}
}

/// Encode a single byte of a base128 value
const fn encode_base128_byte(mut self, mut n: u32, i: usize, continued: bool) -> Self {
const fn encode_base128_byte(mut self, mut n: u32, i: usize, continued: bool) -> Result<Self> {
let mask = if continued { 0b10000000 } else { 0 };

if n > 0x80 {
self.bytes[self.cursor + i] = (n & 0b1111111) as u8 | mask;
n >>= 7;

assert!(i > 0, "Base 128 offset miscalculation");
self.encode_base128_byte(n, i.saturating_sub(1), true)
if i > 0 {
self.encode_base128_byte(n, i.saturating_sub(1), true)
} else {
Err(Error::Base128)
}
} else {
self.bytes[self.cursor] = n as u8 | mask;
self
Ok(self)
}
}
}
Expand All @@ -116,7 +135,7 @@ pub(crate) fn write_base128(bytes: &mut [u8], mut n: Arc) -> Result<usize> {
let mut mask = 0;

while n > 0x80 {
let byte = bytes.get_mut(i).ok_or(Error)?;
let byte = bytes.get_mut(i).ok_or(Error::Length)?;
*byte = (n & 0b1111111 | mask) as u8;
n >>= 7;
i = i.checked_sub(1).expect("overflow");
Expand All @@ -139,12 +158,12 @@ mod tests {
#[test]
fn encode() {
let encoder = Encoder::new();
let encoder = encoder.encode(1);
let encoder = encoder.encode(2);
let encoder = encoder.encode(840);
let encoder = encoder.encode(10045);
let encoder = encoder.encode(2);
let encoder = encoder.encode(1);
let encoder = encoder.encode(1).unwrap();
let encoder = encoder.encode(2).unwrap();
let encoder = encoder.encode(840).unwrap();
let encoder = encoder.encode(10045).unwrap();
let encoder = encoder.encode(2).unwrap();
let encoder = encoder.encode(1).unwrap();
assert_eq!(&encoder.bytes[..encoder.cursor], EXAMPLE_OID_BER);
}
}
54 changes: 50 additions & 4 deletions const-oid/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,63 @@
//! Error types

use crate::Arc;
use core::fmt;

/// Result type
pub type Result<T> = core::result::Result<T, Error>;

/// Error type
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Error;
/// OID errors.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Error {
/// Arc exceeds allowed range (i.e. for first or second OID)
ArcInvalid {
/// Arc value that is erroneous.
arc: Arc,
},

/// Arc is too big (exceeds 32-bit limits of this library).
///
/// Technically the size of an arc is not constrained by X.660, however
/// this library has elected to use `u32` as the arc representation as
/// sufficient for PKIX/PKCS usages.
ArcTooBig,

/// Base 128 encoding error (used in BER/DER serialization of arcs).
Base128,

/// Expected a digit, but was provided something else.
DigitExpected {
/// What was found instead of a digit
actual: u8,
},

/// Input data is empty.
Empty,

/// OID length is invalid (too short or too long).
Length,

/// Minimum 3 arcs required.
NotEnoughArcs,

/// Trailing `.` character at end of input.
TrailingDot,
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("OID error")
match *self {
Error::ArcInvalid { arc } => write!(f, "OID contains out-of-range arc: {}", arc),
Error::ArcTooBig => f.write_str("OID contains arc which is larger than 32-bits"),
Error::Base128 => f.write_str("OID contains arc with invalid base 128 encoding"),
Error::DigitExpected { actual } => {
write!(f, "expected digit, got '{}'", char::from(actual))
}
Error::Empty => f.write_str("OID value is empty"),
Error::Length => f.write_str("OID length invalid"),
Error::NotEnoughArcs => f.write_str("OID requires minimum of 3 arcs"),
Error::TrailingDot => f.write_str("OID ends with invalid trailing '.'"),
}
}
}

Expand Down
Loading

0 comments on commit 8d33af4

Please sign in to comment.