Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

der: Generic custom-class long > 30 tags PRIVATE, CONTEXT-SPECIFIC, APPLICATION #1545

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
15 changes: 14 additions & 1 deletion der/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
mod internal_macros;

mod any;
mod any_custom_class;
mod application;
mod bit_string;
#[cfg(feature = "alloc")]
mod bmp_string;
mod boolean;
mod choice;
mod context_specific;
mod custom_class;
mod generalized_time;
mod ia5_string;
mod integer;
Expand All @@ -20,6 +23,7 @@ mod octet_string;
mod oid;
mod optional;
mod printable_string;
mod private;
#[cfg(feature = "real")]
mod real;
mod sequence;
Expand All @@ -32,15 +36,24 @@ mod videotex_string;

pub use self::{
any::AnyRef,
any_custom_class::{AnyCustomClassExplicit, AnyCustomClassImplicit},
application::{
ApplicationExplicit, ApplicationExplicitRef, ApplicationImplicit, ApplicationImplicitRef,
},
bit_string::{BitStringIter, BitStringRef},
choice::Choice,
context_specific::{ContextSpecific, ContextSpecificRef},
//context_specific::{ContextSpecific, ContextSpecificRef},
context_specific::{
ContextSpecificExplicit, ContextSpecificExplicitRef, ContextSpecificImplicit,
ContextSpecificImplicitRef,
},
generalized_time::GeneralizedTime,
ia5_string::Ia5StringRef,
integer::{int::IntRef, uint::UintRef},
null::Null,
octet_string::OctetStringRef,
printable_string::PrintableStringRef,
private::{PrivateExplicit, PrivateExplicitRef, PrivateImplicit, PrivateImplicitRef},
sequence::{Sequence, SequenceRef},
sequence_of::{SequenceOf, SequenceOfIter},
set_of::{SetOf, SetOfIter},
Expand Down
261 changes: 261 additions & 0 deletions der/src/asn1/any_custom_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
use crate::{
Class, Decode, DecodeValue, Encode, EncodeValue, Error, Header, Length, Reader, Tag, TagNumber,
Tagged, Writer,
};

use super::AnyRef;

/// `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// `EXPLICIT` encoding - always constructed.
pub struct AnyCustomClassExplicit<T> {
/// Value of the field. Should implement [`Decode`]
pub value: T,

/// Class of the field.
///
/// Supported classes: [`Class::Application`], [`Class::ContextSpecific`], [`Class::Private`]
pub class: Class,

/// Tag number without the leading class bits `0b11000000`
/// and without constructed `0b00100000` flag.
pub tag_number: TagNumber,
}

/// `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// `IMPLICIT` encoding - constructed bit should match inner value's tag.
pub struct AnyCustomClassImplicit<T> {
/// Value of the field. Should implement [`DecodeValue`]
pub value: T,

/// Class of the field.
///
/// Supported classes: [`Class::Application`], [`Class::ContextSpecific`], [`Class::Private`]
pub class: Class,

/// Tag number without the leading class bits `0b11000000`
/// and without constructed `0b00100000` flag.
pub tag_number: TagNumber,

/// Constructed flag. Should match value's tag constructed flag.
pub constructed: bool,
}

impl<'a, T> AnyCustomClassExplicit<T>
where
T: Decode<'a>,
{
/// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag.
///
/// Skips `CONTEXT-SPECIFIC` fields, lower than [`TagNumber`].
pub fn decode_skipping<R: Reader<'a>>(
class: Class,
tag_number: TagNumber,
reader: &mut R,
) -> Result<Option<Self>, T::Error> {
decode_peeking(reader, class, tag_number, |reader| {
Self::decode_checked(class, tag_number, reader)
})
}

/// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag.
pub fn decode_checked<R: Reader<'a>>(
class: Class,
tag_number: TagNumber,
reader: &mut R,
) -> Result<Self, T::Error> {
let any_explicit = Self::decode(reader)?;

if any_explicit.class == class && any_explicit.tag_number == tag_number {
Ok(any_explicit)
} else {
let expected = expected_tag_constructed(class, tag_number, true);
Err(any_explicit.tag().unexpected_error(Some(expected)).into())
}
}
}

impl<'a, T> AnyCustomClassImplicit<T>
where
T: Tagged + DecodeValue<'a> + 'a,
{
/// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag.
///
/// Skips `CONTEXT-SPECIFIC` fields, lower than [`TagNumber`].
pub fn decode_skipping<R: Reader<'a>>(
class: Class,
tag_number: TagNumber,
reader: &mut R,
) -> Result<Option<Self>, T::Error> {
decode_peeking::<_, _, T::Error, _>(reader, class, tag_number, |reader| {
Self::decode_checked(class, tag_number, reader)
})
}

/// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value.
///
/// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag.
pub fn decode_checked<R: Reader<'a>>(
class: Class,
tag_number: TagNumber,
reader: &mut R,
) -> Result<Self, T::Error> {
let any_implicit = Self::decode(reader)?;
if any_implicit.class == class && any_implicit.tag_number == tag_number {
Ok(any_implicit)
} else {
let expected = expected_tag_constructed(class, tag_number, true);
Err(any_implicit.tag().unexpected_error(Some(expected)).into())
}
}
}

impl<'a, T> Decode<'a> for AnyCustomClassExplicit<T>
where
T: Decode<'a>,
{
type Error = T::Error;

fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self, Self::Error> {
let header = Header::decode(reader)?;

if !header.tag.is_constructed() {
return Err(header.tag.non_canonical_error().into());
}

Ok(Self {
value: reader.read_nested(header.length, |reader| T::decode(reader))?,
class: header.tag.class(),
tag_number: header.tag.number(),
})
}
}

impl<'a, T> Decode<'a> for AnyCustomClassImplicit<T>
where
T: Tagged + DecodeValue<'a> + 'a,
{
type Error = T::Error;

fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self, Self::Error> {
let header = Header::decode(reader)?;

let value = reader.read_nested(header.length, |reader| T::decode_value(reader, header))?;

if header.tag.is_constructed() != value.tag().is_constructed() {
return Err(header.tag.non_canonical_error().into());
}
Ok(Self {
value,
class: header.tag.class(),
tag_number: header.tag.number(),
constructed: header.tag.is_constructed(),
})
}
}

impl<T> EncodeValue for AnyCustomClassExplicit<T>
where
T: EncodeValue + Tagged,
{
fn value_len(&self) -> Result<Length, Error> {
self.value.encoded_len()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> {
self.value.encode(writer)
}
}

impl<T> EncodeValue for AnyCustomClassImplicit<T>
where
T: EncodeValue + Tagged,
{
fn value_len(&self) -> Result<Length, Error> {
self.value.value_len()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> {
self.value.encode_value(writer)
}
}

impl<T> Tagged for AnyCustomClassExplicit<T> {
fn tag(&self) -> Tag {
expected_tag_constructed(self.class, self.tag_number, true)
}
}

impl<T> Tagged for AnyCustomClassImplicit<T> {
fn tag(&self) -> Tag {
expected_tag_constructed(self.class, self.tag_number, self.constructed)
}
}

/// Attempt to decode a custom class-tagged field with the given
/// helper callback.
fn decode_peeking<'a, F, R: Reader<'a>, E, T>(
reader: &mut R,
expected_class: Class,
expected_number: TagNumber,
f: F,
) -> Result<Option<T>, E>
where
F: FnOnce(&mut R) -> Result<T, E>,
E: From<Error>,
{
while let Some(tag) = Tag::peek_optional(reader)? {
if is_unskippable_tag(tag, expected_class, expected_number) {
break;
} else if tag.number() == expected_number {
return Some(f(reader)).transpose();
} else {
AnyRef::decode(reader)?;
}
}

Ok(None)
}

/// Returns if this tag is of different class than eg. CONTEXT-SPECIFIC
/// or tag number is higher than expected
fn is_unskippable_tag(tag: Tag, expected_class: Class, expected_number: TagNumber) -> bool {
if expected_class != tag.class() {
return true;
}
match expected_class {
Class::Application => tag.number() > expected_number,
Class::ContextSpecific => tag.number() > expected_number,
Class::Private => tag.number() != expected_number,
Class::Universal => tag.number() != expected_number,
}
}

pub(crate) const fn expected_tag_constructed(
class: Class,
number: TagNumber,
constructed: bool,
) -> Tag {
match class {
Class::Application => Tag::Application {
constructed,
number,
},
Class::ContextSpecific => Tag::ContextSpecific {
constructed,
number,
},
Class::Private => Tag::Private {
constructed,
number,
},
Class::Universal => Tag::Null,
}
}
48 changes: 48 additions & 0 deletions der/src/asn1/application.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//! Application field.

use crate::tag::CLASS_APPLICATION;

use super::custom_class::{
CustomClassExplicit, CustomClassExplicitRef, CustomClassImplicit, CustomClassImplicitRef,
};

/// Application class, EXPLICIT
pub type ApplicationExplicit<const TAG: u16, T> = CustomClassExplicit<TAG, T, CLASS_APPLICATION>;

/// Application class, IMPLICIT
pub type ApplicationImplicit<const TAG: u16, T> = CustomClassImplicit<TAG, T, CLASS_APPLICATION>;

/// Application class, reference, EXPLICIT
pub type ApplicationExplicitRef<'a, const TAG: u16, T> =
CustomClassExplicitRef<'a, TAG, T, CLASS_APPLICATION>;

/// Application class, reference, IMPLICIT
pub type ApplicationImplicitRef<'a, const TAG: u16, T> =
CustomClassImplicitRef<'a, TAG, T, CLASS_APPLICATION>;

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use crate::{
asn1::{context_specific::ContextSpecificExplicit, OctetStringRef},
Decode, Encode,
};
use hex_literal::hex;

#[test]
fn round_trip() {
const EXAMPLE_BYTES: &[u8] = &hex!(
"A2 06"
"04 04"
"01020304"
);

let field =
ContextSpecificExplicit::<2, OctetStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap();
assert_eq!(field.value, OctetStringRef::new(&[1, 2, 3, 4]).unwrap());

let mut buf = [0u8; 128];
let encoded = field.encode_to_slice(&mut buf).unwrap();
assert_eq!(encoded, EXAMPLE_BYTES);
}
}
Loading
Loading