Skip to content

Commit 347049d

Browse files
committed
der: add ClassTaggedExplicit wrapper
Generic wrapper around `APPLICATION`, `CONTEXT-SPECIFIC` and `PRIVATE` tags. Such struct simplifies decoding, as previously we had to assert that decoded tag number is the expected one.
1 parent 950e25e commit 347049d

File tree

6 files changed

+149
-10
lines changed

6 files changed

+149
-10
lines changed

der/src/asn1.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub(crate) mod bit_string;
1111
mod bmp_string;
1212
mod boolean;
1313
mod choice;
14+
mod class_tagged;
1415
mod context_specific;
1516
mod general_string;
1617
mod generalized_time;
@@ -35,18 +36,18 @@ mod videotex_string;
3536

3637
pub use self::{
3738
any::AnyRef,
38-
application::{Application, ApplicationRef},
39+
application::{Application, ApplicationExplicit, ApplicationRef},
3940
bit_string::{BitStringIter, BitStringRef},
4041
choice::Choice,
41-
context_specific::{ContextSpecific, ContextSpecificRef},
42+
context_specific::{ContextSpecific, ContextSpecificExplicit, ContextSpecificRef},
4243
general_string::GeneralStringRef,
4344
generalized_time::GeneralizedTime,
4445
ia5_string::Ia5StringRef,
4546
integer::{int::IntRef, uint::UintRef},
4647
null::Null,
4748
octet_string::OctetStringRef,
4849
printable_string::PrintableStringRef,
49-
private::{Private, PrivateRef},
50+
private::{Private, PrivateExplicit, PrivateRef},
5051
sequence::{Sequence, SequenceRef},
5152
sequence_of::{SequenceOf, SequenceOfIter},
5253
set_of::{SetOf, SetOfIter},

der/src/asn1/application.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
33
use crate::{
44
Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
5-
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef,
6-
tag::IsConstructed,
5+
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer,
6+
asn1::{AnyRef, class_tagged::ClassTaggedExplicit},
7+
tag::{IsConstructed, class::CLASS_APPLICATION},
78
};
89
use core::cmp::Ordering;
910

@@ -12,3 +13,7 @@ use crate::ErrorKind;
1213

1314
impl_custom_class!(Application, Application, "APPLICATION", "0b01000000");
1415
impl_custom_class_ref!(ApplicationRef, Application, "APPLICATION", "0b01000000");
16+
17+
/// Application class, EXPLICIT
18+
pub type ApplicationExplicit<const NUMBER: u32, T> =
19+
ClassTaggedExplicit<NUMBER, T, CLASS_APPLICATION>;

der/src/asn1/class_tagged.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use crate::{
2+
Class, Decode, DecodeValue, Encode, EncodeValue, Error, FixedTag, Header, Length, Reader, Tag,
3+
TagMode, TagNumber, Writer,
4+
};
5+
6+
/// `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` reference, with const `EXPLICIT` encoding.
7+
///
8+
///
9+
/// This type encodes a field which is specific to a particular context
10+
/// and is identified by a [`TagNumber`].
11+
///
12+
/// Inner value might implement [`Encode`], [`Decode`] or both.
13+
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
14+
pub struct ClassTaggedExplicit<const NUMBER: u32, T, const CLASS_BITS: u8> {
15+
/// Inner value might implement [`Encode`], [`Decode`] or both.
16+
pub value: T,
17+
}
18+
19+
impl<const NUMBER: u32, T, const CLASS_BITS: u8> ClassTaggedExplicit<NUMBER, T, CLASS_BITS> {
20+
/// Returns const [`TagNumber`], associated with this `EXPLICIT` tag wrapper.
21+
pub const fn tag_number() -> TagNumber {
22+
TagNumber(NUMBER)
23+
}
24+
25+
/// Returns const [`TagMode::Explicit`], associated with this `EXPLICIT` tag wrapper.
26+
pub const fn tag_mode() -> TagMode {
27+
TagMode::Explicit
28+
}
29+
}
30+
31+
impl<const NUMBER: u32, T, const CLASS_BITS: u8> EncodeValue
32+
for ClassTaggedExplicit<NUMBER, T, CLASS_BITS>
33+
where
34+
T: Encode,
35+
{
36+
fn value_len(&self) -> Result<Length, Error> {
37+
self.value.encoded_len()
38+
}
39+
40+
fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> {
41+
// Encode EXPLICIT value (with tag and length)
42+
self.value.encode(writer)
43+
}
44+
}
45+
46+
impl<'a, const NUMBER: u32, T, const CLASS_BITS: u8> DecodeValue<'a>
47+
for ClassTaggedExplicit<NUMBER, T, CLASS_BITS>
48+
where
49+
T: Decode<'a>,
50+
{
51+
type Error = T::Error;
52+
53+
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self, Self::Error> {
54+
// encoding shall be constructed
55+
if !header.tag().is_constructed() {
56+
return Err(reader.error(header.tag().non_canonical_error()).into());
57+
}
58+
Ok(Self {
59+
value: T::decode(reader)?,
60+
})
61+
}
62+
}
63+
64+
impl<const NUMBER: u32, T, const CLASS_BITS: u8> FixedTag
65+
for ClassTaggedExplicit<NUMBER, T, CLASS_BITS>
66+
{
67+
const TAG: Tag = Tag::new_non_universal(Class::from_bits(CLASS_BITS), TagNumber(NUMBER), true);
68+
}

der/src/asn1/context_specific.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
33
use crate::{
44
Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
5-
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef,
6-
tag::IsConstructed,
5+
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer,
6+
asn1::{AnyRef, class_tagged::ClassTaggedExplicit},
7+
tag::{IsConstructed, class::CLASS_CONTEXT_SPECIFIC},
78
};
89
use core::cmp::Ordering;
910

@@ -23,13 +24,17 @@ impl_custom_class_ref!(
2324
"0b10000000"
2425
);
2526

27+
/// ContextSpecific class, EXPLICIT
28+
pub type ContextSpecificExplicit<const NUMBER: u32, T> =
29+
ClassTaggedExplicit<NUMBER, T, CLASS_CONTEXT_SPECIFIC>;
30+
2631
#[cfg(test)]
2732
#[allow(clippy::unwrap_used)]
2833
mod tests {
2934
use super::ContextSpecific;
3035
use crate::{
3136
Decode, Encode, SliceReader, TagMode, TagNumber,
32-
asn1::{BitStringRef, ContextSpecificRef, SetOf, Utf8StringRef},
37+
asn1::{BitStringRef, ContextSpecificExplicit, ContextSpecificRef, SetOf, Utf8StringRef},
3338
};
3439
use hex_literal::hex;
3540

@@ -194,4 +199,34 @@ mod tests {
194199
assert_eq!(field.value.get(0).cloned(), Some(hello));
195200
assert_eq!(field.value.get(1).cloned(), Some(world));
196201
}
202+
203+
#[test]
204+
fn round_trip_explicit() {
205+
let field =
206+
ContextSpecificExplicit::<1, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap();
207+
assert_eq!(
208+
field.value,
209+
BitStringRef::from_bytes(&EXAMPLE_BYTES[5..]).unwrap()
210+
);
211+
assert_eq!(
212+
ContextSpecificExplicit::<1, BitStringRef<'_>>::tag_mode(),
213+
TagMode::Explicit
214+
);
215+
assert_eq!(
216+
ContextSpecificExplicit::<1, BitStringRef<'_>>::tag_number(),
217+
TagNumber(1)
218+
);
219+
220+
let mut buf = [0u8; 128];
221+
let encoded = field.encode_to_slice(&mut buf).unwrap();
222+
assert_eq!(encoded, EXAMPLE_BYTES);
223+
224+
// should not decode as tag CONTEXT-SPECIFIC [2]
225+
assert!(ContextSpecificExplicit::<2, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).is_err());
226+
227+
// should be different than CONTEXT-SPECIFIC [1]
228+
let invalid_field = ContextSpecificExplicit::<2, BitStringRef<'_>> { value: field.value };
229+
let invalid_encoded = invalid_field.encode_to_slice(&mut buf).unwrap();
230+
assert_ne!(invalid_encoded, EXAMPLE_BYTES);
231+
}
197232
}

der/src/asn1/private.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
33
use crate::{
44
Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
5-
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef,
5+
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer,
6+
asn1::{AnyRef, class_tagged::ClassTaggedExplicit},
67
tag::IsConstructed,
8+
tag::class::CLASS_PRIVATE,
79
};
810
use core::cmp::Ordering;
911

@@ -12,3 +14,6 @@ use crate::ErrorKind;
1214

1315
impl_custom_class!(Private, Private, "PRIVATE", "0b11000000");
1416
impl_custom_class_ref!(PrivateRef, Private, "PRIVATE", "0b11000000");
17+
18+
/// Private class, EXPLICIT
19+
pub type PrivateExplicit<const NUMBER: u32, T> = ClassTaggedExplicit<NUMBER, T, CLASS_PRIVATE>;

der/src/tag.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! ASN.1 tags.
22
#![cfg_attr(feature = "arbitrary", allow(clippy::arithmetic_side_effects))]
33

4-
mod class;
4+
pub(crate) mod class;
55
mod mode;
66
mod number;
77

@@ -165,6 +165,31 @@ impl Tag {
165165
/// rules implemented by this crate.
166166
pub(crate) const MAX_SIZE: usize = 6;
167167

168+
/// Creates a [`Tag`] of non-`UNIVERSAL` class.
169+
/// Allowed classes are:
170+
/// - `APPLICATION` [`Class::Application`],
171+
/// - `CONTEXT-SPECIFIC` [`Class::ContextSpecific`],
172+
/// - `PRIVATE` [`Class::Private`],
173+
///
174+
/// Returns [`Tag::Null`] otherwise.
175+
pub const fn new_non_universal(class: Class, number: TagNumber, constructed: bool) -> Tag {
176+
match class {
177+
Class::Application => Tag::Application {
178+
constructed,
179+
number,
180+
},
181+
Class::ContextSpecific => Tag::ContextSpecific {
182+
constructed,
183+
number,
184+
},
185+
Class::Private => Tag::Private {
186+
constructed,
187+
number,
188+
},
189+
Class::Universal => Tag::Null,
190+
}
191+
}
192+
168193
/// Decode a [`Tag`] in addition to returning the value of the constructed bit.
169194
pub(crate) fn decode_with_constructed_bit<'a>(
170195
reader: &mut impl Reader<'a>,

0 commit comments

Comments
 (0)