Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 169 additions & 35 deletions der/derive/src/attributes.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,184 @@
//! Attribute-related types used by the proc macro

use crate::Asn1Type;
use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
use crate::{Asn1Type, TagMode, TagNumber};
use proc_macro2::TokenStream;
use quote::quote;
use std::{fmt::Debug, str::FromStr};
use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta, Path};

#[derive(Debug)]
pub(crate) struct Asn1Attrs {
/// Value of the `#[asn1(type = "...")]` attribute if provided
/// Attribute name.
const ATTR_NAME: &str = "asn1";

/// Parsing error message.
const PARSE_ERR_MSG: &str = "error parsing `asn1` attribute";

/// Maximum tag number supported (inclusive).
pub const TAG_NUMBER_MAX: TagNumber = 30;

/// Attributes on a `struct` or `enum` type.
#[derive(Clone, Debug)]
pub(crate) struct TypeAttrs {
/// Tagging mode for this type's ASN.1 module: `EXPLICIT` or `IMPLICIT`,
/// supplied as `#[asn1(tag_mode = "...")]`.
///
/// The default value is `EXPLICIT`.
pub tag_mode: TagMode,
}

impl TypeAttrs {
/// Parse attributes from a struct field or enum variant.
pub fn parse(attrs: &[Attribute]) -> Self {
let mut tag_mode = None;

for attr in attrs.iter().map(AttrNameValue::new).flatten() {
// `tag_mode = "..."` attribute
if let Some(mode) = attr.parse_value("tag_mode") {
if tag_mode.is_some() {
panic!("duplicate ASN.1 `tag_mode` attribute: {}", attr.value);
}

tag_mode = Some(mode);
} else {
panic!(
"unknown field-level `asn1` attribute: {:?} \
(valid options are `tag_mode`)",
attr.name
);
}
}

Self {
tag_mode: tag_mode.unwrap_or_default(),
}
}
}

/// Field-level attributes.
#[derive(Clone, Debug)]
pub(crate) struct FieldAttrs {
/// Value of the `#[asn1(type = "...")]` attribute if provided.
pub asn1_type: Option<Asn1Type>,

/// Indicate that this field is a context-specific field with the given
/// context-specific tag number.
// TODO(tarcieri): backend support
#[allow(dead_code)]
pub context_specific: Option<TagNumber>,
}

impl Asn1Attrs {
/// Parse attributes from a field or enum variant
pub fn new(attrs: &[Attribute]) -> Self {
impl FieldAttrs {
/// Parse attributes from a struct field or enum variant.
pub fn parse(attrs: &[Attribute]) -> Self {
let mut asn1_type = None;
let mut context_specific = None;

for attr in attrs {
if !attr.path.is_ident("asn1") {
continue;
}
for attr in attrs.iter().map(AttrNameValue::new).flatten() {
// `context_specific = "..."` attribute
if let Some(tag_number) = attr.parse_value("context_specific") {
if context_specific.is_some() {
panic!(
"duplicate ASN.1 `context_specific` attribute: {}",
tag_number
);
}

if tag_number > TAG_NUMBER_MAX {
panic!(
"error parsing `context_specific` tag number (too big): {}",
tag_number
);
}

match attr.parse_meta().expect("error parsing `asn1` attribute") {
Meta::List(MetaList { nested, .. }) if nested.len() == 1 => {
match nested.first() {
Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(lit_str),
..
}))) => {
// Parse the `type = "..."` attribute
if !path.is_ident("type") {
panic!("unknown `asn1` attribute: {:?}", path);
}

if let Some(ty) = asn1_type {
panic!("duplicate ASN.1 `type` attribute: {:?}", ty);
}

asn1_type = Some(Asn1Type::new(&lit_str.value()));
}
other => panic!("malformed `asn1` attribute: {:?}", other),
}
context_specific = Some(tag_number);
// `type = "..."` attribute
} else if let Some(ty) = attr.parse_value("type") {
if asn1_type.is_some() {
panic!("duplicate ASN.1 `type` attribute: {}", attr.value);
}
other => panic!("malformed `asn1` attribute: {:?}", other),

asn1_type = Some(ty);
} else {
panic!(
"unknown field-level `asn1` attribute: {:?} \
(valid options are `context_specific`, `type`)",
attr.name
);
}
}

Self { asn1_type }
Self {
asn1_type,
context_specific,
}
}

/// Get a `der::Decoder` object which respects these field attributes.
pub fn decoder(&self, _type_attrs: &TypeAttrs) -> TokenStream {
self.asn1_type
.map(|ty| ty.decoder())
.unwrap_or_else(|| quote!(decoder.decode()))
}

/// Get a `der::Encoder` object which respects these field attributes.
pub fn encoder(&self, binding: &TokenStream, _type_attrs: &TypeAttrs) -> TokenStream {
self.asn1_type
.map(|ty| ty.encoder(binding))
.unwrap_or_else(|| quote!(encoder.encode(#binding)))
}
}

/// Name/value pair attribute.
struct AttrNameValue {
/// Attribute name.
pub name: Path,

/// Attribute value.
pub value: String,
}

impl AttrNameValue {
/// Parse a given attribute.
///
/// Returns `None` if the attribute name is anything other than `asn1`.
///
/// Expects the value is a string literal.
pub fn new(attr: &Attribute) -> Option<Self> {
if !attr.path.is_ident(ATTR_NAME) {
return None;
}

let nested = match attr.parse_meta().expect(PARSE_ERR_MSG) {
Meta::List(MetaList { nested, .. }) if nested.len() == 1 => nested,
other => panic!("malformed `asn1` attribute: {:?}", other),
};

match nested.first() {
Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(lit_str),
..
}))) => Some(Self {
name: path.clone(),
value: lit_str.value(),
}),
_ => panic!("malformed `asn1` attribute: {:?}", nested),
}
}

/// Parse an attribute value if the name matches the specified one.
pub fn parse_value<T>(&self, name: &str) -> Option<T>
where
T: FromStr + Debug,
T::Err: Debug,
{
if self.name.is_ident(name) {
Some(
self.value
.parse()
.unwrap_or_else(|_| panic!("error parsing `{}` attribute", name)),
)
} else {
None
}
}
}
40 changes: 17 additions & 23 deletions der/derive/src/choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! the purposes of decoding/encoding ASN.1 `CHOICE` types as mapped to
//! enum variants.

use crate::{Asn1Attrs, Asn1Type};
use crate::{Asn1Type, FieldAttrs, TypeAttrs};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{DataEnum, Fields, FieldsUnnamed, Ident, Lifetime, Type, Variant};
Expand All @@ -13,6 +13,9 @@ type Alternatives = std::collections::BTreeMap<Asn1Type, Alternative>;

/// Derive the `Choice` trait for an enum.
pub(crate) struct DeriveChoice {
/// `asn1` attributes defined at the type level.
type_attrs: TypeAttrs,

/// `CHOICE` alternatives for this enum.
alternatives: Alternatives,

Expand All @@ -39,6 +42,7 @@ impl DeriveChoice {
);

let mut state = Self {
type_attrs: TypeAttrs::parse(&s.ast().attrs),
alternatives: Default::default(),
choice_body: TokenStream::new(),
decode_body: TokenStream::new(),
Expand All @@ -47,21 +51,23 @@ impl DeriveChoice {
};

for (variant_info, variant) in s.variants().iter().zip(&data.variants) {
let asn1_type = Asn1Attrs::new(&variant.attrs).asn1_type.unwrap_or_else(|| {
let field_attrs = FieldAttrs::parse(&variant.attrs);
let asn1_type = field_attrs.asn1_type.unwrap_or_else(|| {
panic!(
"no #[asn1(type=...)] specified for enum variant: {}",
variant.ident
)
});
let tag = asn1_type.tag();

Alternative::register(&mut state.alternatives, asn1_type, variant);
state.derive_variant_choice(asn1_type);
state.derive_variant_decoder(asn1_type);
state.derive_variant_choice(&tag);
state.derive_variant_decoder(&tag, &field_attrs);

match variant_info.bindings().len() {
// TODO(tarcieri): handle 0 bindings for ASN.1 NULL
1 => {
state.derive_variant_encoder(variant_info, asn1_type);
state.derive_variant_encoder(variant_info, &field_attrs);
state.derive_variant_encoded_len(variant_info);
}
other => panic!(
Expand All @@ -75,35 +81,23 @@ impl DeriveChoice {
}

/// Derive the body of `Choice::can_decode
fn derive_variant_choice(&mut self, asn1_type: Asn1Type) {
let tag = asn1_type.tag();

fn derive_variant_choice(&mut self, tag: &TokenStream) {
if self.choice_body.is_empty() {
tag
tag.clone()
} else {
quote!(| #tag)
}
.to_tokens(&mut self.choice_body);
}

/// Derive a match arm of the impl body for `TryFrom<der::asn1::Any<'_>>`.
fn derive_variant_decoder(&mut self, asn1_type: Asn1Type) {
let tag = asn1_type.tag();

let decoder = match asn1_type {
Asn1Type::BitString => quote!(decoder.bit_string()),
Asn1Type::GeneralizedTime => quote!(decoder.generalized_time()),
Asn1Type::OctetString => quote!(decoder.octet_string()),
Asn1Type::PrintableString => quote!(decoder.printable_string()),
Asn1Type::UtcTime => quote!(decoder.utc_time()),
Asn1Type::Utf8String => quote!(decoder.utf8_string()),
};

fn derive_variant_decoder(&mut self, tag: &TokenStream, field_attrs: &FieldAttrs) {
let decoder = field_attrs.decoder(&self.type_attrs);
{ quote!(#tag => Ok(#decoder?.try_into()?),) }.to_tokens(&mut self.decode_body);
}

/// Derive a match arm for the impl body for `der::Encodable::encode`.
fn derive_variant_encoder(&mut self, variant: &VariantInfo<'_>, asn1_type: Asn1Type) {
fn derive_variant_encoder(&mut self, variant: &VariantInfo<'_>, field_attrs: &FieldAttrs) {
assert_eq!(
variant.bindings().len(),
1,
Expand All @@ -113,7 +107,7 @@ impl DeriveChoice {
variant
.each(|bi| {
let binding = &bi.binding;
let encoder_obj = asn1_type.encoder(quote!(#binding));
let encoder_obj = field_attrs.encoder(&quote!(#binding), &self.type_attrs);
quote!(#encoder_obj?.encode(encoder))
})
.to_tokens(&mut self.encode_body);
Expand Down
Loading