Skip to content

Commit

Permalink
support private tags and tag numbers >30 that are stored in long form
Browse files Browse the repository at this point in the history
  • Loading branch information
Harris Kaufmann committed May 23, 2024
1 parent 6c42217 commit 67e77fb
Show file tree
Hide file tree
Showing 17 changed files with 716 additions and 227 deletions.
107 changes: 86 additions & 21 deletions der/derive/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(crate) struct FieldAttrs {
pub asn1_type: Option<Asn1Type>,

/// Value of the `#[asn1(context_specific = "...")] attribute if provided.
pub context_specific: Option<TagNumber>,
pub class: Option<Class>,

/// Indicates name of function that supplies the default value, which will be used in cases
/// where encoding is omitted per DER and to omit the encoding per DER
Expand Down Expand Up @@ -94,7 +94,7 @@ impl FieldAttrs {
/// Parse attributes from a struct field or enum variant.
pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> syn::Result<Self> {
let mut asn1_type = None;
let mut context_specific = None;
let mut class = None;
let mut default = None;
let mut extensible = None;
let mut optional = None;
Expand All @@ -107,11 +107,24 @@ impl FieldAttrs {
for attr in parsed_attrs {
// `context_specific = "..."` attribute
if let Some(tag_number) = attr.parse_value("context_specific")? {
if context_specific.is_some() {
abort!(attr.name, "duplicate ASN.1 `context_specific` attribute");
if class.is_some() {
abort!(
attr.name,
"duplicate ASN.1 class attribute (`context_specific`, `private`)"
);
}

context_specific = Some(tag_number);
class = Some(Class::ContextSpecific(tag_number));
// `private = "..."` attribute
} else if let Some(tag_number) = attr.parse_value("private")? {
if class.is_some() {
abort!(
attr.name,
"duplicate ASN.1 class attribute (`context_specific`, `private`)"
);
}

class = Some(Class::Private(tag_number));
// `default` attribute
} else if attr.parse_value::<String>("default")?.is_some() {
if default.is_some() {
Expand Down Expand Up @@ -170,7 +183,7 @@ impl FieldAttrs {

Ok(Self {
asn1_type,
context_specific,
class,
default,
extensible: extensible.unwrap_or_default(),
optional: optional.unwrap_or_default(),
Expand All @@ -181,11 +194,8 @@ impl FieldAttrs {

/// Get the expected [`Tag`] for this field.
pub fn tag(&self) -> syn::Result<Option<Tag>> {
match self.context_specific {
Some(tag_number) => Ok(Some(Tag::ContextSpecific {
constructed: self.constructed,
number: tag_number,
})),
match self.class {
Some(ref class) => Ok(Some(class.get_tag(self.constructed))),

None => match self.tag_mode {
TagMode::Explicit => Ok(self.asn1_type.map(Tag::Universal)),
Expand All @@ -199,22 +209,27 @@ impl FieldAttrs {

/// Get a `der::Decoder` object which respects these field attributes.
pub fn decoder(&self) -> TokenStream {
if let Some(tag_number) = self.context_specific {
if let Some(ref class) = self.class {
let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default();
let tag_number = tag_number.to_tokens();
let ClassTokens {
tag_type,
tag_number,
class_type,
..
} = class.to_tokens();

let context_specific = match self.tag_mode {
TagMode::Explicit => {
if self.extensible || self.is_optional() {
quote! {
::der::asn1::ContextSpecific::<#type_params>::decode_explicit(
#class_type::<#type_params>::decode_explicit(
reader,
#tag_number
)?
}
} else {
quote! {
match ::der::asn1::ContextSpecific::<#type_params>::decode(reader)? {
match #class_type::<#type_params>::decode(reader)? {
field if field.tag_number == #tag_number => Some(field),
_ => None
}
Expand All @@ -223,7 +238,7 @@ impl FieldAttrs {
}
TagMode::Implicit => {
quote! {
::der::asn1::ContextSpecific::<#type_params>::decode_implicit(
#class_type::<#type_params>::decode_implicit(
reader,
#tag_number
)?
Expand All @@ -242,7 +257,7 @@ impl FieldAttrs {
let constructed = self.constructed;
quote! {
#context_specific.ok_or_else(|| {
der::Tag::ContextSpecific {
#tag_type {
number: #tag_number,
constructed: #constructed
}.value_error()
Expand All @@ -265,12 +280,17 @@ impl FieldAttrs {

/// Get tokens to encode the binding using `::der::EncodeValue`.
pub fn value_encode(&self, binding: &TokenStream) -> TokenStream {
match self.context_specific {
Some(tag_number) => {
let tag_number = tag_number.to_tokens();
match self.class {
Some(ref class) => {
let ClassTokens {
tag_number,
ref_type,
..
} = class.to_tokens();
let tag_mode = self.tag_mode.to_tokens();

quote! {
::der::asn1::ContextSpecificRef {
#ref_type {
tag_number: #tag_number,
tag_mode: #tag_mode,
value: #binding,
Expand Down Expand Up @@ -360,3 +380,48 @@ impl AttrNameValue {
})
}
}

#[derive(Clone, Debug)]
pub(crate) enum Class {
ContextSpecific(TagNumber),
Private(TagNumber),
}

pub(crate) struct ClassTokens {
pub tag_type: TokenStream,
pub tag_number: TokenStream,
pub class_type: TokenStream,
pub ref_type: TokenStream,
}

impl Class {
pub fn to_tokens(&self) -> ClassTokens {
match self {
Self::ContextSpecific(tag_number) => ClassTokens {
tag_type: quote!(::der::Tag::ContextSpecific),
tag_number: tag_number.to_tokens(),
class_type: quote!(::der::asn1::ContextSpecific),
ref_type: quote!(::der::asn1::ContextSpecificRef),
},
Self::Private(tag_number) => ClassTokens {
tag_type: quote!(::der::Tag::Private),
tag_number: tag_number.to_tokens(),
class_type: quote!(::der::asn1::Private),
ref_type: quote!(::der::asn1::PrivateRef),
},
}
}

pub fn get_tag(&self, constructed: bool) -> Tag {
match self {
Class::ContextSpecific(number) => Tag::ContextSpecific {
constructed,
number: *number,
},
Class::Private(number) => Tag::Private {
constructed,
number: *number,
},
}
}
}
7 changes: 6 additions & 1 deletion der/derive/src/choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ impl DeriveChoice {

fn decode<R: ::der::Reader<#lifetime>>(reader: &mut R) -> ::der::Result<Self> {
use der::Reader as _;
match reader.peek_tag()? {

let tag = reader
.peek_tag()?
.ok_or_else(|| der::Error::incomplete(reader.input_len()))?;

match tag {
#(#decode_body)*
actual => Err(der::ErrorKind::TagUnexpected {
expected: None,
Expand Down
24 changes: 16 additions & 8 deletions der/derive/src/choice/variant.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Choice variant IR and lowerings
use crate::{FieldAttrs, Tag, TypeAttrs};
use crate::{attributes::ClassTokens, FieldAttrs, Tag, TypeAttrs};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Fields, Ident, Path, Type, Variant};
Expand Down Expand Up @@ -123,13 +123,18 @@ impl ChoiceVariant {
pub(super) fn to_value_len_tokens(&self) -> TokenStream {
let ident = &self.ident;

match self.attrs.context_specific {
Some(tag_number) => {
let tag_number = tag_number.to_tokens();
match self.attrs.class {
Some(ref class) => {
let ClassTokens {
tag_number,
ref_type,
..
} = class.to_tokens();

let tag_mode = self.attrs.tag_mode.to_tokens();

quote! {
Self::#ident(variant) => ::der::asn1::ContextSpecificRef {
Self::#ident(variant) => #ref_type {
tag_number: #tag_number,
tag_mode: #tag_mode,
value: variant,
Expand All @@ -154,7 +159,10 @@ impl ChoiceVariant {
#[cfg(test)]
mod tests {
use super::ChoiceVariant;
use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber};
use crate::{
attributes::Class, choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode,
TagNumber,
};
use proc_macro2::Span;
use quote::quote;
use syn::Ident;
Expand Down Expand Up @@ -254,7 +262,7 @@ mod tests {
let ident = Ident::new("ExplicitVariant", Span::call_site());
let attrs = FieldAttrs {
constructed,
context_specific: Some(TagNumber(tag_number)),
class: Some(Class::ContextSpecific(TagNumber(tag_number))),
..Default::default()
};
assert_eq!(attrs.tag_mode, TagMode::Explicit);
Expand Down Expand Up @@ -339,7 +347,7 @@ mod tests {

let attrs = FieldAttrs {
constructed,
context_specific: Some(TagNumber(tag_number)),
class: Some(Class::ContextSpecific(TagNumber(tag_number))),
tag_mode: TagMode::Implicit,
..Default::default()
};
Expand Down
40 changes: 21 additions & 19 deletions der/derive/src/sequence/field.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Sequence field IR and lowerings
use crate::{Asn1Type, FieldAttrs, TagMode, TagNumber, TypeAttrs};
use crate::{
attributes::{Class, ClassTokens},
Asn1Type, FieldAttrs, TagMode, TypeAttrs,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Field, Ident, Path, Type};
Expand Down Expand Up @@ -66,7 +69,7 @@ impl SequenceField {
);

// TODO(tarcieri): support for context-specific fields with defaults?
if self.attrs.context_specific.is_none() {
if self.attrs.class.is_none() {
lowerer.apply_default(default, &self.field_type);
}
}
Expand All @@ -88,8 +91,8 @@ impl SequenceField {
lowerer.apply_asn1_type(ty, attrs.optional);
}

if let Some(tag_number) = &attrs.context_specific {
lowerer.apply_context_specific(tag_number, &attrs.tag_mode, attrs.optional);
if let Some(class) = &attrs.class {
lowerer.apply_class(class, &attrs.tag_mode, attrs.optional)
}

if let Some(default) = &attrs.default {
Expand Down Expand Up @@ -201,31 +204,30 @@ impl LowerFieldEncoder {
};
}

/// Make this field context-specific.
fn apply_context_specific(
&mut self,
tag_number: &TagNumber,
tag_mode: &TagMode,
optional: bool,
) {
/// Apply the non-universal class.
fn apply_class(&mut self, class: &Class, tag_mode: &TagMode, optional: bool) {
let encoder = &self.encoder;
let number_tokens = tag_number.to_tokens();
let mode_tokens = tag_mode.to_tokens();
let ClassTokens {
tag_number,
ref_type,
..
} = class.to_tokens();

if optional {
self.encoder = quote! {
#encoder.as_ref().map(|field| {
::der::asn1::ContextSpecificRef {
tag_number: #number_tokens,
#ref_type {
tag_number: #tag_number,
tag_mode: #mode_tokens,
value: field,
}
})
};
} else {
self.encoder = quote! {
::der::asn1::ContextSpecificRef {
tag_number: #number_tokens,
#ref_type {
tag_number: #tag_number,
tag_mode: #mode_tokens,
value: &#encoder,
}
Expand All @@ -237,7 +239,7 @@ impl LowerFieldEncoder {
#[cfg(test)]
mod tests {
use super::SequenceField;
use crate::{FieldAttrs, TagMode, TagNumber};
use crate::{attributes::Class, FieldAttrs, TagMode, TagNumber};
use proc_macro2::Span;
use quote::quote;
use syn::{punctuated::Punctuated, Ident, Path, PathSegment, Type, TypePath};
Expand Down Expand Up @@ -266,7 +268,7 @@ mod tests {

let attrs = FieldAttrs {
asn1_type: None,
context_specific: None,
class: None,
default: None,
extensible: false,
optional: false,
Expand Down Expand Up @@ -306,7 +308,7 @@ mod tests {

let attrs = FieldAttrs {
asn1_type: None,
context_specific: Some(TagNumber(0)),
class: Some(Class::ContextSpecific(TagNumber(0))),
default: None,
extensible: false,
optional: false,
Expand Down
Loading

0 comments on commit 67e77fb

Please sign in to comment.