Skip to content

Commit 0977fc1

Browse files
committed
[WIP] der: remove lifetime from BitStringRef [BREAKING]
Following the pattern of #1921 and what #1998 did for `OctetStringRef`, this removes the lifetime from the struct, instead changing `BitStringRef` to a proper reference type to be used as `&BitStringRef`, which can implement the `Borrow` and `ToOwned` patterns needed to work with `Cow`. To make this work, this uses a hack described in #2298 where we abuse a fat pointer, namely a ZST slice `[UnsafeCell<()>]`, to carry the pointer to the `*const u8` buffer that backs the `BitStringRef`, and carries the bit length along as the length of the ZST slice. To get the original byte length back we can `div_ceil(8)` and then reconstruct the original `[u8]`.
1 parent b11f134 commit 0977fc1

2 files changed

Lines changed: 209 additions & 0 deletions

File tree

der/src/asn1.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod internal_macros;
77
mod any;
88
mod application;
99
pub(crate) mod bit_string;
10+
mod bit_string2;
1011
#[cfg(feature = "alloc")]
1112
mod bmp_string;
1213
mod boolean;
@@ -37,6 +38,7 @@ pub use self::{
3738
any::AnyRef,
3839
application::{Application, ApplicationRef},
3940
bit_string::{BitStringIter, BitStringRef},
41+
bit_string2::BitStringRef2,
4042
choice::Choice,
4143
context_specific::{ContextSpecific, ContextSpecificRef},
4244
general_string::GeneralStringRef,

der/src/asn1/bit_string2.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
//! ASN.1 `BIT STRING` support.
2+
3+
use crate::{Length, Result, Tag};
4+
use core::{
5+
cell::UnsafeCell,
6+
fmt::{self, Debug},
7+
ptr, slice,
8+
};
9+
10+
/// Inaccessible placeholder ZST which is sound to construct in any length.
11+
///
12+
/// Using [`UnsafeCell`] prevents the compiler from reasoning about aliasing of the unknown type.
13+
type Inner = UnsafeCell<()>;
14+
15+
/// ASN.1 `BIT STRING` reference type.
16+
///
17+
/// This type contains a sequence of any number of bits.
18+
///
19+
/// This is a zero-copy reference type which borrows from the input data.
20+
#[repr(transparent)]
21+
pub struct BitStringRef2 {
22+
/// Fat pointer which represents the bit string as a slice with a given number of bits.
23+
inner: [Inner],
24+
}
25+
26+
impl BitStringRef2 {
27+
/// Create a new ASN.1 `BIT STRING` from a byte slice.
28+
///
29+
/// Accepts an optional number of "unused bits" (0-7) which are omitted from the final octet.
30+
/// This number is 0 if the value is octet-aligned.
31+
///
32+
/// # Errors
33+
/// Returns an error if any of the following occur:
34+
/// - `unused_bits` is invalid
35+
/// - `bytes` is too long
36+
/// - an overflow occurred calculating the bit length
37+
#[allow(unsafe_code)]
38+
pub fn new<'a>(unused_bits: u8, bytes: &'a [u8]) -> Result<&'a Self> {
39+
let bits = bit_length(unused_bits, bytes)?;
40+
41+
// Create a slice that stores the original pointer to `bytes` and `bits` as its length.
42+
// SAFETY: `Inner` is a ZST so we can construct slices of any length so long as the pointer
43+
// is valid.
44+
let slice = unsafe { slice::from_raw_parts::<'a, Inner>(bytes.as_ptr().cast(), bits) };
45+
46+
// SAFETY: `Self` is a `repr(transparent)` newtype for `[UnsafeCell<()>]`.
47+
Ok(unsafe { &*(ptr::from_ref(slice) as *const Self) })
48+
}
49+
50+
/// Create a new ASN.1 `BIT STRING` from the given bytes.
51+
///
52+
/// The "unused bits" are set to 0.
53+
///
54+
/// # Errors
55+
/// Has the same error cases as [`BitStringRef2::new`].
56+
pub fn from_bytes(bytes: &[u8]) -> Result<&Self> {
57+
Self::new(0, bytes)
58+
}
59+
60+
/// Borrow the inner byte slice.
61+
///
62+
/// Returns `None` if the number of unused bits is *not* equal to zero,
63+
/// i.e. if the `BIT STRING` is not octet aligned.
64+
///
65+
/// Use [`BitString::raw_bytes`] to obtain access to the raw value
66+
/// regardless of the presence of unused bits.
67+
#[must_use]
68+
pub fn as_bytes(&self) -> Option<&[u8]> {
69+
if self.has_unused_bits() {
70+
None
71+
} else {
72+
Some(self.raw_bytes())
73+
}
74+
}
75+
76+
/// Borrow the raw bytes of this `BIT STRING`.
77+
///
78+
/// Note that the byte string may contain extra unused bits in the final octet.
79+
///
80+
/// If the number of unused bits is expected to be 0, the [`BitStringRef2::as_bytes`] function
81+
/// can be used instead.
82+
#[must_use]
83+
pub fn raw_bytes(&self) -> &[u8] {
84+
// SAFETY: `byte_length` computes the original length of the byte slice this `BitStringRef`
85+
// was constructed from, and `inner` contains the original pointer.
86+
#[allow(unsafe_code)]
87+
unsafe {
88+
slice::from_raw_parts(self.inner.as_ptr().cast(), byte_length(self.inner.len()))
89+
}
90+
}
91+
92+
/// Returns `Some(bit)` if index is valid.
93+
#[must_use]
94+
pub fn get(&self, position: usize) -> Option<bool> {
95+
if position >= self.bit_len() {
96+
return None;
97+
}
98+
99+
let byte = self.raw_bytes().get(position / 8)?;
100+
let bitmask = 1u8 << (7 - (position % 8));
101+
Some(byte & bitmask != 0)
102+
}
103+
104+
/// Get the length of this `BIT STRING` in bits.
105+
#[must_use]
106+
pub fn bit_len(&self) -> usize {
107+
self.inner.len()
108+
}
109+
110+
/// Get the length of this `BIT STRING` in bytes.
111+
#[must_use]
112+
#[allow(clippy::missing_panics_doc, reason = "should not panic in practice")]
113+
pub fn byte_len(&self) -> Length {
114+
Length::new_usize(byte_length(self.bit_len())).expect("arithmetic error")
115+
}
116+
117+
/// Get the number of unused bits in this byte slice.
118+
#[must_use]
119+
pub fn unused_bits(&self) -> u8 {
120+
match self.unaligned_bits() {
121+
0 => 0,
122+
n => 8 - n,
123+
}
124+
}
125+
126+
/// Is the number of unused bits a value other than 0?
127+
#[must_use]
128+
pub fn has_unused_bits(&self) -> bool {
129+
self.unaligned_bits() != 0
130+
}
131+
132+
/// Is this bit string empty?
133+
#[must_use]
134+
pub fn is_empty(&self) -> bool {
135+
self.inner.is_empty()
136+
}
137+
138+
/// Get the number of bits which aren't aligned to a byte.
139+
#[must_use]
140+
#[allow(clippy::cast_possible_truncation, reason = "masked to fit")]
141+
fn unaligned_bits(&self) -> u8 {
142+
(self.bit_len() & 0b111) as u8
143+
}
144+
}
145+
146+
impl Debug for BitStringRef2 {
147+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148+
f.debug_struct("BitStringRef2")
149+
.field("inner", &self.raw_bytes())
150+
.field("unused_bits", &self.unused_bits())
151+
.finish_non_exhaustive()
152+
}
153+
}
154+
155+
/// Compute the length of a `BIT STRING` in bits given `unused_bits` and its `bytes`.
156+
fn bit_length(unused_bits: u8, bytes: &[u8]) -> Result<usize> {
157+
match bytes
158+
.len()
159+
.checked_mul(8)
160+
.and_then(|b| b.checked_sub(unused_bits.into()))
161+
{
162+
Some(bits) if unused_bits < 8 => Ok(bits),
163+
_ => Err(Tag::BitString.value_error().into()),
164+
}
165+
}
166+
167+
/// Compute the length of a `BIT STRING` in bytes from its length in bits.
168+
fn byte_length(bits: usize) -> usize {
169+
bits.div_ceil(8)
170+
}
171+
172+
#[cfg(test)]
173+
mod tests {
174+
use super::BitStringRef2;
175+
176+
#[test]
177+
fn bits() {
178+
let bytes = [0u8, 1, 2];
179+
let aligned = BitStringRef2::new(0, &bytes).unwrap();
180+
assert_eq!(aligned.bit_len(), 24);
181+
assert_eq!(aligned.byte_len(), bytes.len().try_into().unwrap());
182+
assert_eq!(aligned.unused_bits(), 0);
183+
assert!(!aligned.has_unused_bits());
184+
185+
let unaligned = BitStringRef2::new(1, &bytes).unwrap();
186+
assert_eq!(unaligned.bit_len(), 23);
187+
assert_eq!(aligned.byte_len(), bytes.len().try_into().unwrap());
188+
assert_eq!(unaligned.unused_bits(), 1);
189+
assert!(unaligned.has_unused_bits());
190+
}
191+
192+
#[test]
193+
fn raw_bytes() {
194+
let bytes = [0u8, 1, 2];
195+
let aligned = BitStringRef2::new(0, &bytes).unwrap();
196+
assert_eq!(aligned.raw_bytes(), &bytes);
197+
198+
let unaligned = BitStringRef2::new(1, &bytes).unwrap();
199+
assert_eq!(unaligned.raw_bytes(), &bytes);
200+
}
201+
202+
#[test]
203+
fn too_many_unused_bits() {
204+
assert!(BitStringRef2::new(1, &[]).is_err());
205+
assert!(BitStringRef2::new(8, &[0, 1, 2]).is_err());
206+
}
207+
}

0 commit comments

Comments
 (0)