diff --git a/der/src/asn1.rs b/der/src/asn1.rs index 60a7b2dec..425c36a91 100644 --- a/der/src/asn1.rs +++ b/der/src/asn1.rs @@ -35,10 +35,10 @@ mod videotex_string; pub use self::{ any::AnyRef, - application::{Application, ApplicationRef}, + application::{Application, ApplicationExplicit, ApplicationRef}, bit_string::{BitStringIter, BitStringRef}, choice::Choice, - context_specific::{ContextSpecific, ContextSpecificRef}, + context_specific::{ContextSpecific, ContextSpecificExplicit, ContextSpecificRef}, general_string::GeneralStringRef, generalized_time::GeneralizedTime, ia5_string::Ia5StringRef, @@ -46,7 +46,7 @@ pub use self::{ null::Null, octet_string::OctetStringRef, printable_string::PrintableStringRef, - private::{Private, PrivateRef}, + private::{Private, PrivateExplicit, PrivateRef}, sequence::{Sequence, SequenceRef}, sequence_of::{SequenceOf, SequenceOfIter}, set_of::{SetOf, SetOfIter}, diff --git a/der/src/asn1/application.rs b/der/src/asn1/application.rs index 2bedc237e..9b69cf774 100644 --- a/der/src/asn1/application.rs +++ b/der/src/asn1/application.rs @@ -2,8 +2,8 @@ use crate::{ Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, EncodingRules, - Error, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef, - tag::IsConstructed, + Error, FixedTag, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, + asn1::AnyRef, tag::IsConstructed, }; use core::cmp::Ordering; @@ -12,3 +12,10 @@ use crate::ErrorKind; impl_custom_class!(Application, Application, "APPLICATION", "0b01000000"); impl_custom_class_ref!(ApplicationRef, Application, "APPLICATION", "0b01000000"); + +impl_custom_class_explicit!( + ApplicationExplicit, + Application, + "APPLICATION", + "0b01000000" +); diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index 9c7701050..d75ab0e14 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -2,8 +2,8 @@ use crate::{ Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, EncodingRules, - Error, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef, - tag::IsConstructed, + Error, FixedTag, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, + asn1::AnyRef, tag::IsConstructed, }; use core::cmp::Ordering; @@ -23,13 +23,23 @@ impl_custom_class_ref!( "0b10000000" ); +impl_custom_class_explicit!( + ContextSpecificExplicit, + ContextSpecific, + "CONTEXT-SPECIFIC", + "0b10000000" +); + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use super::ContextSpecific; use crate::{ Decode, Encode, SliceReader, TagMode, TagNumber, - asn1::{BitStringRef, ContextSpecificRef, SetOf, Utf8StringRef}, + asn1::{ + BitStringRef, ContextSpecificRef, SetOf, Utf8StringRef, + context_specific::ContextSpecificExplicit, + }, }; use hex_literal::hex; @@ -194,4 +204,34 @@ mod tests { assert_eq!(field.value.get(0).cloned(), Some(hello)); assert_eq!(field.value.get(1).cloned(), Some(world)); } + + #[test] + fn round_trip_explicit() { + let field = + ContextSpecificExplicit::<1, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); + assert_eq!( + field.value, + BitStringRef::from_bytes(&EXAMPLE_BYTES[5..]).unwrap() + ); + assert_eq!( + ContextSpecificExplicit::<1, BitStringRef<'_>>::tag_mode(), + TagMode::Explicit + ); + assert_eq!( + ContextSpecificExplicit::<1, BitStringRef<'_>>::tag_number(), + TagNumber(1) + ); + + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, EXAMPLE_BYTES); + + // should not decode as tag CONTEXT-SPECIFIC [2] + assert!(ContextSpecificExplicit::<2, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).is_err()); + + // should be different than CONTEXT-SPECIFIC [1] + let invalid_field = ContextSpecificExplicit::<2, BitStringRef<'_>> { value: field.value }; + let invalid_encoded = invalid_field.encode_to_slice(&mut buf).unwrap(); + assert_ne!(invalid_encoded, EXAMPLE_BYTES); + } } diff --git a/der/src/asn1/internal_macros.rs b/der/src/asn1/internal_macros.rs index 29c8c8c9d..96a0297af 100644 --- a/der/src/asn1/internal_macros.rs +++ b/der/src/asn1/internal_macros.rs @@ -335,3 +335,73 @@ macro_rules! impl_custom_class_ref { } }; } + +macro_rules! impl_custom_class_explicit { + ($explicit_class_type_name: ident, $class_enum_name: ident, $asn1_class_name: literal, $class_bits_str: literal) => { + #[doc = concat!("`", $asn1_class_name, "` field reference, with const `EXPLICIT` encoding.")] + /// + /// + /// This type encodes a field which is specific to a particular context + /// and is identified by a [`TagNumber`]. + /// + /// Inner value might implement [`Encode`], [`Decode`] or both. + #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] + pub struct $explicit_class_type_name { + /// Inner value might implement [`Encode`], [`Decode`] or both. + pub value: T, + } + + impl $explicit_class_type_name { + #[doc = concat!("Returns const [`TagNumber`], associated with this `EXPLICIT` `", $asn1_class_name, "` wrapper.")] + pub const fn tag_number() -> TagNumber { + TagNumber(NUMBER) + } + + #[doc = concat!("Returns const [`TagMode::Explicit`], associated with this `EXPLICIT` `", $asn1_class_name, "` wrapper.")] + pub const fn tag_mode() -> TagMode { + TagMode::Explicit + } + } + + impl EncodeValue for $explicit_class_type_name + where + T: Encode, + { + fn value_len(&self) -> Result { + self.value.encoded_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + // Encode EXPLICIT value (with tag and length) + self.value.encode(writer) + } + } + + impl<'a, const NUMBER: u32, T> DecodeValue<'a> for $explicit_class_type_name + where + T: Decode<'a>, + { + type Error = T::Error; + + fn decode_value>( + reader: &mut R, + header: Header, + ) -> Result { + // encoding shall be constructed + if !header.tag.is_constructed() { + return Err(reader.error(header.tag.non_canonical_error()).into()); + } + Ok(Self { + value: T::decode(reader)?, + }) + } + } + + impl FixedTag for $explicit_class_type_name { + const TAG: Tag = Tag::$class_enum_name { + constructed: true, + number: Self::tag_number(), + }; + } + }; +} diff --git a/der/src/asn1/private.rs b/der/src/asn1/private.rs index fa48b211a..74a8c1510 100644 --- a/der/src/asn1/private.rs +++ b/der/src/asn1/private.rs @@ -2,8 +2,8 @@ use crate::{ Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, EncodingRules, - Error, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef, - tag::IsConstructed, + Error, FixedTag, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, + asn1::AnyRef, tag::IsConstructed, }; use core::cmp::Ordering; @@ -12,3 +12,4 @@ use crate::ErrorKind; impl_custom_class!(Private, Private, "PRIVATE", "0b11000000"); impl_custom_class_ref!(PrivateRef, Private, "PRIVATE", "0b11000000"); +impl_custom_class_explicit!(PrivateExplicit, Private, "PRIVATE", "0b11000000");