diff --git a/der/derive/src/attributes.rs b/der/derive/src/attributes.rs index 73e6c4f36..e241fe9a2 100644 --- a/der/derive/src/attributes.rs +++ b/der/derive/src/attributes.rs @@ -78,6 +78,9 @@ pub(crate) struct FieldAttrs { /// Inherits from the type-level tagging mode if specified, or otherwise /// defaults to `EXPLICIT`. pub tag_mode: TagMode, + + /// Is the inner type constructed? + pub constructed: bool, } impl FieldAttrs { @@ -97,6 +100,7 @@ impl FieldAttrs { let mut extensible = None; let mut optional = None; let mut tag_mode = None; + let mut constructed = None; let mut parsed_attrs = Vec::new(); AttrNameValue::from_attributes(attrs, &mut parsed_attrs); @@ -146,6 +150,13 @@ impl FieldAttrs { } asn1_type = Some(ty); + // `constructed = "..."` attribute + } else if let Some(ty) = attr.parse_value("constructed") { + if constructed.is_some() { + abort!(attr.name, "duplicate ASN.1 `constructed` attribute: {}"); + } + + constructed = Some(ty); } else { abort!( attr.name, @@ -162,25 +173,22 @@ impl FieldAttrs { extensible: extensible.unwrap_or_default(), optional: optional.unwrap_or_default(), tag_mode: tag_mode.unwrap_or(type_attrs.tag_mode), + constructed: constructed.unwrap_or_default(), } } /// Get the expected [`Tag`] for this field. pub fn tag(&self) -> Option { - match self.tag_mode { - TagMode::Explicit => self.asn1_type.map(Tag::Universal), - TagMode::Implicit => self - .context_specific - .map(|tag_number| { - Some(Tag::ContextSpecific { - // TODO(tarcieri): handle constructed inner types - constructed: false, - number: tag_number, - }) - }) - .unwrap_or_else(|| { - abort_call_site!("implicit tagging requires an associated `tag_number`") - }), + match self.context_specific { + Some(tag_number) => Some(Tag::ContextSpecific { + constructed: self.constructed, + number: tag_number, + }), + + None => match self.tag_mode { + TagMode::Explicit => self.asn1_type.map(Tag::Universal), + TagMode::Implicit => abort_call_site!("implicit tagging requires a `tag_number`"), + }, } } @@ -226,11 +234,12 @@ impl FieldAttrs { } } else { // TODO(tarcieri): better error handling? + let constructed = self.constructed; quote! { #context_specific.ok_or_else(|| { der::Tag::ContextSpecific { number: #tag_number, - constructed: false + constructed: #constructed }.value_error() })?.value } diff --git a/der/derive/src/lib.rs b/der/derive/src/lib.rs index ba95f15e6..320123002 100644 --- a/der/derive/src/lib.rs +++ b/der/derive/src/lib.rs @@ -93,6 +93,11 @@ //! - `UTCTime`: performs an intermediate conversion to [`der::asn1::UtcTime`] //! - `UTF8String`: performs an intermediate conversion to [`der::asn1::Utf8String`] //! +//! ### `#[asn1(constructed = "...")]` attribute: support for constructed inner types +//! +//! This attribute can be used to specify that an "inner" type is constructed. It is most +//! commonly used when a `CHOICE` has a constructed inner type. +//! //! Note: please open a GitHub Issue if you would like to request support //! for additional ASN.1 types. //! diff --git a/der/derive/src/sequence/field.rs b/der/derive/src/sequence/field.rs index 5768998c0..23c85835a 100644 --- a/der/derive/src/sequence/field.rs +++ b/der/derive/src/sequence/field.rs @@ -280,6 +280,7 @@ mod tests { extensible: false, optional: false, tag_mode: TagMode::Explicit, + constructed: false, }; let field_type = Ident::new("String", span); @@ -319,6 +320,7 @@ mod tests { extensible: false, optional: false, tag_mode: TagMode::Implicit, + constructed: false, }; let field_type = Ident::new("String", span);