Skip to content

Commit 28f5e0c

Browse files
authored
const-oid: generic backing buffer (#757)
The current buffering approach, with a fixed-sized `ArrayVec`-like backing buffer, is helpful for being able to use `const fn` to be able to parse OID strings at compile time and re-encode them in BER form. This functionality is preserved as a new `Buffer` type, which still supports all current `const fn` functionality. `ObjectIdentifier` has been changed to be generic around `B: AsRef<[u8]>` which allows a `const fn` constructor that accepts a BER-encoded slice. This is useful for when there are large numbers of OID constants, as in the `const_oid::db` module. However, while this module has been updated so the code compiles, it has not yet been changed to take advantage of this new approach.
1 parent f0238a1 commit 28f5e0c

File tree

5 files changed

+130
-68
lines changed

5 files changed

+130
-68
lines changed

const-oid/src/arcs.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
//! Arcs are integer values which exist within an OID's hierarchy.
22
3-
use crate::{Error, ObjectIdentifier, Result};
3+
use crate::{Error, Result};
44
use core::mem;
55

6+
#[cfg(doc)]
7+
use crate::ObjectIdentifier;
8+
69
/// Type alias used to represent an "arc" (i.e. integer identifier value).
710
///
811
/// X.660 does not define a maximum size of an arc.
@@ -31,17 +34,20 @@ const ARC_MAX_LAST_OCTET: u8 = 0b11110000; // Max bytes of leading 1-bits
3134
///
3235
/// This iterates over all arcs in an OID, including the root.
3336
pub struct Arcs<'a> {
34-
/// OID we're iterating over
35-
oid: &'a ObjectIdentifier,
37+
/// OID bytes we're iterating over.
38+
bytes: &'a [u8],
3639

37-
/// Current position within the serialized DER bytes of this OID
40+
/// Current position within the serialized BER bytes of this OID.
3841
cursor: Option<usize>,
3942
}
4043

4144
impl<'a> Arcs<'a> {
42-
/// Create a new iterator over the arcs of this OID
43-
pub(crate) fn new(oid: &'a ObjectIdentifier) -> Self {
44-
Self { oid, cursor: None }
45+
/// Create a new iterator over an OID encoded as BER bytes.
46+
pub(crate) fn new(bytes: &'a [u8]) -> Self {
47+
Self {
48+
bytes,
49+
cursor: None,
50+
}
4551
}
4652

4753
/// Try to parse the next arc in this OID.
@@ -50,14 +56,14 @@ impl<'a> Arcs<'a> {
5056
/// that the arcs in the OID are well-formed.
5157
pub(crate) fn try_next(&mut self) -> Result<Option<Arc>> {
5258
match self.cursor {
53-
// Indicates we're on the root OID
59+
// Indicates we're on the root arc
5460
None => {
55-
let root = RootArcs::try_from(self.oid.as_bytes()[0])?;
61+
let root = RootArcs::try_from(self.bytes[0])?;
5662
self.cursor = Some(0);
5763
Ok(Some(root.first_arc()))
5864
}
5965
Some(0) => {
60-
let root = RootArcs::try_from(self.oid.as_bytes()[0])?;
66+
let root = RootArcs::try_from(self.bytes[0])?;
6167
self.cursor = Some(1);
6268
Ok(Some(root.second_arc()))
6369
}
@@ -68,7 +74,7 @@ impl<'a> Arcs<'a> {
6874
loop {
6975
let len = checked_add!(offset, arc_bytes);
7076

71-
match self.oid.as_bytes().get(len).cloned() {
77+
match self.bytes.get(len).cloned() {
7278
// The arithmetic below includes advance checks
7379
// against `ARC_MAX_BYTES` and `ARC_MAX_LAST_OCTET`
7480
// which ensure the operations will not overflow.

const-oid/src/buffer.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//! Array-backed buffer for BER bytes.
2+
3+
/// Array-backed buffer for storing BER computed at compile-time.
4+
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
5+
pub struct Buffer<const SIZE: usize> {
6+
/// Length in bytes
7+
pub(crate) length: u8,
8+
9+
/// Array containing BER/DER-serialized bytes (no header)
10+
pub(crate) bytes: [u8; SIZE],
11+
}
12+
13+
impl<const SIZE: usize> Buffer<SIZE> {
14+
/// Borrow the inner byte slice.
15+
pub fn as_bytes(&self) -> &[u8] {
16+
&self.bytes[..self.length as usize]
17+
}
18+
19+
/// Get the length of the BER message.
20+
pub const fn len(&self) -> usize {
21+
self.length as usize
22+
}
23+
24+
/// Const comparison of two buffers.
25+
pub const fn eq(&self, rhs: &Self) -> bool {
26+
if self.length != rhs.length {
27+
return false;
28+
}
29+
30+
let mut i = 0usize;
31+
32+
while i < self.len() {
33+
if self.bytes[i] != rhs.bytes[i] {
34+
return false;
35+
}
36+
37+
// Won't overflow due to `i < self.len()` check above
38+
#[allow(clippy::integer_arithmetic)]
39+
{
40+
i += 1;
41+
}
42+
}
43+
44+
true
45+
}
46+
}
47+
48+
impl<const SIZE: usize> AsRef<[u8]> for Buffer<SIZE> {
49+
fn as_ref(&self) -> &[u8] {
50+
self.as_bytes()
51+
}
52+
}

const-oid/src/db.rs

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,6 @@ pub use gen::*;
1717

1818
use crate::{Error, ObjectIdentifier};
1919

20-
/// A const implementation of byte equals.
21-
const fn eq(lhs: &[u8], rhs: &[u8]) -> bool {
22-
if lhs.len() != rhs.len() {
23-
return false;
24-
}
25-
26-
let mut i = 0usize;
27-
while i < lhs.len() {
28-
if lhs[i] != rhs[i] {
29-
return false;
30-
}
31-
32-
i += 1;
33-
}
34-
35-
true
36-
}
37-
3820
/// A const implementation of case-insensitive ASCII equals.
3921
const fn eq_case(lhs: &[u8], rhs: &[u8]) -> bool {
4022
if lhs.len() != rhs.len() {
@@ -74,7 +56,8 @@ impl<'a> Database<'a> {
7456

7557
while i < self.0.len() {
7658
let lhs = self.0[i].0;
77-
if lhs.length == oid.length && eq(&lhs.bytes, &oid.bytes) {
59+
60+
if lhs.buffer.eq(&oid.buffer) {
7861
return Some(self.0[i].1);
7962
}
8063

const-oid/src/encoder.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::{
44
arcs::{ARC_MAX_FIRST, ARC_MAX_SECOND},
5-
Arc, Error, ObjectIdentifier, Result,
5+
Arc, Buffer, Error, ObjectIdentifier, Result,
66
};
77

88
/// BER/DER encoder
@@ -45,8 +45,8 @@ impl Encoder {
4545
pub(crate) const fn extend(oid: ObjectIdentifier) -> Self {
4646
Self {
4747
state: State::Body,
48-
bytes: oid.bytes,
49-
cursor: oid.length as usize,
48+
bytes: oid.buffer.bytes,
49+
cursor: oid.buffer.length as usize,
5050
}
5151
}
5252

@@ -101,10 +101,12 @@ impl Encoder {
101101
/// Finish encoding an OID.
102102
pub(crate) const fn finish(self) -> Result<ObjectIdentifier> {
103103
if self.cursor >= 2 {
104-
Ok(ObjectIdentifier {
104+
let bytes = Buffer {
105105
bytes: self.bytes,
106106
length: self.cursor as u8,
107-
})
107+
};
108+
109+
Ok(ObjectIdentifier { buffer: bytes })
108110
} else {
109111
Err(Error::NotEnoughArcs)
110112
}

const-oid/src/lib.rs

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
66
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
77
)]
8+
#![allow(clippy::len_without_is_empty)]
89
#![forbid(unsafe_code)]
910
#![warn(
1011
clippy::integer_arithmetic,
@@ -24,6 +25,7 @@ extern crate std;
2425
mod checked;
2526

2627
mod arcs;
28+
mod buffer;
2729
mod encoder;
2830
mod error;
2931
mod parser;
@@ -34,12 +36,18 @@ pub mod db;
3436

3537
pub use crate::{
3638
arcs::{Arc, Arcs},
39+
buffer::Buffer,
3740
error::{Error, Result},
3841
};
3942

4043
use crate::encoder::Encoder;
4144
use core::{fmt, str::FromStr};
4245

46+
/// Default maximum size.
47+
///
48+
/// Makes `ObjectIdentifier` 40-bytes total w\ 1-byte length.
49+
const MAX_SIZE: usize = 39;
50+
4351
/// A trait which associates an OID with a type.
4452
pub trait AssociatedOid {
4553
/// The OID associated with this type.
@@ -78,18 +86,14 @@ impl<T: AssociatedOid> DynAssociatedOid for T {
7886
/// - The BER/DER encoding of the OID MUST be shorter than
7987
/// [`ObjectIdentifier::MAX_SIZE`]
8088
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
81-
pub struct ObjectIdentifier {
82-
/// Length in bytes
83-
length: u8,
84-
85-
/// Array containing BER/DER-serialized bytes (no header)
86-
bytes: [u8; Self::MAX_SIZE],
89+
pub struct ObjectIdentifier<B: AsRef<[u8]> = Buffer<MAX_SIZE>> {
90+
/// Buffer containing BER/DER-serialized bytes (sans ASN.1 tag/length)
91+
buffer: B,
8792
}
8893

89-
#[allow(clippy::len_without_is_empty)]
9094
impl ObjectIdentifier {
9195
/// Maximum size of a BER/DER-encoded OID in bytes.
92-
pub const MAX_SIZE: usize = 39; // makes `ObjectIdentifier` 40-bytes total w\ 1-byte length
96+
pub const MAX_SIZE: usize = MAX_SIZE;
9397

9498
/// Parse an [`ObjectIdentifier`] from the dot-delimited string form,
9599
/// panicking on parse errors.
@@ -145,27 +149,60 @@ impl ObjectIdentifier {
145149
3..=Self::MAX_SIZE => (),
146150
_ => return Err(Error::NotEnoughArcs),
147151
}
152+
148153
let mut bytes = [0u8; Self::MAX_SIZE];
149154
bytes[..len].copy_from_slice(ber_bytes);
150155

151-
let oid = Self {
156+
let bytes = Buffer {
152157
bytes,
153158
length: len as u8,
154159
};
155160

161+
let oid = Self { buffer: bytes };
162+
156163
// Ensure arcs are well-formed
157164
let mut arcs = oid.arcs();
158165
while arcs.try_next()?.is_some() {}
159166

160167
Ok(oid)
161168
}
162169

170+
/// Get the parent OID of this one (if applicable).
171+
pub fn parent(&self) -> Option<Self> {
172+
let num_arcs = self.len().checked_sub(1)?;
173+
Self::from_arcs(self.arcs().take(num_arcs)).ok()
174+
}
175+
176+
/// Push an additional arc onto this OID, returning the child OID.
177+
pub const fn push_arc(self, arc: Arc) -> Result<Self> {
178+
// TODO(tarcieri): use `?` when stable in `const fn`
179+
match Encoder::extend(self).arc(arc) {
180+
Ok(encoder) => encoder.finish(),
181+
Err(err) => Err(err),
182+
}
183+
}
184+
}
185+
186+
impl<'a> ObjectIdentifier<&'a [u8]> {
187+
/// Initialize OID from a byte slice without validating that it contains
188+
/// a well-formed BER-encoded OID.
189+
///
190+
/// Use with care, e.g. to define compact constants.
191+
pub const fn from_bytes_unchecked(buffer: &'a [u8]) -> Self {
192+
Self { buffer }
193+
}
194+
}
195+
196+
impl<B> ObjectIdentifier<B>
197+
where
198+
B: AsRef<[u8]>,
199+
{
163200
/// Get the BER/DER serialization of this OID as bytes.
164201
///
165202
/// Note that this encoding omits the tag/length, and only contains the
166203
/// value portion of the encoded OID.
167204
pub fn as_bytes(&self) -> &[u8] {
168-
&self.bytes[..self.length as usize]
205+
self.buffer.as_ref()
169206
}
170207

171208
/// Return the arc with the given index, if it exists.
@@ -177,31 +214,19 @@ impl ObjectIdentifier {
177214
///
178215
/// Returns [`Arcs`], an iterator over [`Arc`] values.
179216
pub fn arcs(&self) -> Arcs<'_> {
180-
Arcs::new(self)
217+
Arcs::new(self.buffer.as_ref())
181218
}
182219

183220
/// Get the length of this [`ObjectIdentifier`] in arcs.
184221
pub fn len(&self) -> usize {
185222
self.arcs().count()
186223
}
187-
188-
/// Get the parent OID of this one (if applicable).
189-
pub fn parent(&self) -> Option<Self> {
190-
let num_arcs = self.len().checked_sub(1)?;
191-
Self::from_arcs(self.arcs().take(num_arcs)).ok()
192-
}
193-
194-
/// Push an additional arc onto this OID, returning the child OID.
195-
pub const fn push_arc(self, arc: Arc) -> Result<Self> {
196-
// TODO(tarcieri): use `?` when stable in `const fn`
197-
match Encoder::extend(self).arc(arc) {
198-
Ok(encoder) => encoder.finish(),
199-
Err(err) => Err(err),
200-
}
201-
}
202224
}
203225

204-
impl AsRef<[u8]> for ObjectIdentifier {
226+
impl<B> AsRef<[u8]> for ObjectIdentifier<B>
227+
where
228+
B: AsRef<[u8]>,
229+
{
205230
fn as_ref(&self) -> &[u8] {
206231
self.as_bytes()
207232
}
@@ -223,12 +248,6 @@ impl TryFrom<&[u8]> for ObjectIdentifier {
223248
}
224249
}
225250

226-
impl From<&ObjectIdentifier> for ObjectIdentifier {
227-
fn from(oid: &ObjectIdentifier) -> ObjectIdentifier {
228-
*oid
229-
}
230-
}
231-
232251
impl fmt::Debug for ObjectIdentifier {
233252
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234253
write!(f, "ObjectIdentifier({})", self)

0 commit comments

Comments
 (0)