diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index c282d1cda..6be80bfc9 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -3,6 +3,7 @@ use crate::{ Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef, + tag::IsConstructed, }; use core::cmp::Ordering; @@ -60,7 +61,7 @@ impl ContextSpecific { tag_number: TagNumber, ) -> Result, T::Error> where - T: DecodeValue<'a> + Tagged, + T: DecodeValue<'a> + IsConstructed, { Self::decode_with::<_, _, T::Error>(reader, tag_number, |reader| { // Decode IMPLICIT header @@ -72,7 +73,7 @@ impl ContextSpecific { T::decode_value(reader, header) })?; - if header.tag.is_constructed() != value.tag().is_constructed() { + if header.tag.is_constructed() != T::CONSTRUCTED { return Err(header.tag.non_canonical_error().into()); } diff --git a/der/src/lib.rs b/der/src/lib.rs index cd0b88cc0..a247b8a56 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -377,7 +377,7 @@ pub use crate::{ length::{IndefiniteLength, Length}, ord::{DerOrd, ValueOrd}, reader::{Reader, slice::SliceReader}, - tag::{Class, FixedTag, Tag, TagMode, TagNumber, Tagged}, + tag::{Class, FixedTag, IsConstructed, Tag, TagMode, TagNumber, Tagged}, writer::{Writer, slice::SliceWriter}, }; diff --git a/der/src/tag.rs b/der/src/tag.rs index da2260d6c..ce374a319 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -32,6 +32,17 @@ impl Tagged for T { } } +/// Types which have a constant ASN.1 constructed bit. +pub trait IsConstructed { + /// ASN.1 constructed bit + const CONSTRUCTED: bool; +} + +/// Types which are [`FixedTag`] always known if they are constructed (or primitive). +impl IsConstructed for T { + const CONSTRUCTED: bool = T::TAG.is_constructed(); +} + /// ASN.1 tags. /// /// Tags are the leading identifier octet of the Tag-Length-Value encoding @@ -229,7 +240,7 @@ impl Tag { } /// Does this tag represent a constructed (as opposed to primitive) field? - pub fn is_constructed(self) -> bool { + pub const fn is_constructed(self) -> bool { match self { Tag::Sequence | Tag::Set => true, Tag::Application { constructed, .. } diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 818f3cc1b..376223075 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -112,7 +112,7 @@ mod choice { /// `Choice` with `IMPLICIT` tagging. mod implicit { use der::{ - Choice, Decode, Encode, SliceWriter, + Choice, Decode, Encode, Sequence, SliceWriter, asn1::{BitStringRef, GeneralizedTime}, }; use hex_literal::hex; @@ -179,6 +179,13 @@ mod choice { cs_time.encode(&mut encoder).unwrap(); assert_eq!(TIME_DER, encoder.finish().unwrap()); } + + /// Test case for `CHOICE` inside `[0]` `EXPLICIT` tag in `SEQUENCE`. + #[derive(Sequence, Debug, Eq, PartialEq)] + pub struct ExplicitChoiceInsideSequence<'a> { + #[asn1(tag_mode = "EXPLICIT", context_specific = "0")] + choice_field: ImplicitChoice<'a>, + } } } @@ -743,6 +750,21 @@ mod decode_value { } } +/// Custom derive test cases for the `DecodeValue` + `EncodeValue` macro combo. +mod decode_encode_value { + use der::{DecodeValue, EncodeValue, IsConstructed}; + + /// Example of a structure, that does not have a tag and is not a sequence + /// but can be encoded as `[0] IMPLICIT` + #[derive(DecodeValue, EncodeValue, Default, Eq, PartialEq, Debug)] + struct DecodeEncodeCheck { + field: bool, + } + impl IsConstructed for DecodeEncodeCheck { + const CONSTRUCTED: bool = true; + } +} + /// Custom derive test cases for the `BitString` macro. #[cfg(feature = "std")] mod bitstring { diff --git a/der_derive/src/choice.rs b/der_derive/src/choice.rs index 4c2b8e28f..6ca474802 100644 --- a/der_derive/src/choice.rs +++ b/der_derive/src/choice.rs @@ -97,6 +97,10 @@ impl DeriveChoice { } } + impl #impl_generics ::der::IsConstructed for #ident #ty_generics #where_clause { + const CONSTRUCTED: bool = true; + } + impl #impl_generics ::der::Decode<#lifetime> for #ident #ty_generics #where_clause { type Error = #error;