blob: c765e190c37a592281749a37ff375603871e67ca [file] [log] [blame]
//! Attribute-related types used by the proc macro
use crate::{Asn1Type, Tag, TagMode, TagNumber};
use proc_macro2::TokenStream;
use proc_macro_error::{abort, abort_call_site};
use quote::quote;
use std::{fmt::Debug, str::FromStr};
use syn::{Attribute, Lit, LitStr, Meta, MetaList, MetaNameValue, NestedMeta, Path};
/// Attribute name.
pub(crate) const ATTR_NAME: &str = "asn1";
/// Parsing error message.
const PARSE_ERR_MSG: &str = "error parsing `asn1` attribute";
/// Attributes on a `struct` or `enum` type.
#[derive(Clone, Debug, Default)]
pub(crate) struct TypeAttrs {
/// Tagging mode for this type: `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;
let mut parsed_attrs = Vec::new();
AttrNameValue::from_attributes(attrs, &mut parsed_attrs);
for attr in parsed_attrs {
// `tag_mode = "..."` attribute
if let Some(mode) = attr.parse_value("tag_mode") {
if tag_mode.is_some() {
abort!(attr.name, "duplicate ASN.1 `tag_mode` attribute");
}
tag_mode = Some(mode);
} else {
abort!(
attr.name,
"invalid `asn1` attribute (valid options are `tag_mode`)",
);
}
}
Self {
tag_mode: tag_mode.unwrap_or_default(),
}
}
}
/// Field-level attributes.
#[derive(Clone, Debug, Default)]
pub(crate) struct FieldAttrs {
/// Value of the `#[asn1(type = "...")]` attribute if provided.
pub asn1_type: Option<Asn1Type>,
/// Value of the `#[asn1(context_specific = "...")] attribute if provided.
pub context_specific: Option<TagNumber>,
/// 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
pub default: Option<Path>,
/// Is this field "extensible", i.e. preceded by the `...` extensibility marker?
pub extensible: bool,
/// Is this field `OPTIONAL`?
pub optional: bool,
/// Tagging mode for this type: `EXPLICIT` or `IMPLICIT`, supplied as
/// `#[asn1(tag_mode = "...")]`.
///
/// 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 {
/// Return true when either an optional or default ASN.1 attribute is associated
/// with a field. Default signifies optionality due to omission of default values in
/// DER encodings.
fn is_optional(&self) -> bool {
self.optional || self.default.is_some()
}
/// Parse attributes from a struct field or enum variant.
pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> Self {
let mut asn1_type = None;
let mut context_specific = None;
let mut default = None;
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);
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");
}
context_specific = Some(tag_number);
// `default` attribute
} else if attr.parse_value::<String>("default").is_some() {
if default.is_some() {
abort!(attr.name, "duplicate ASN.1 `default` attribute");
}
default = Some(attr.value.parse().unwrap_or_else(|e| {
abort!(attr.value, "error parsing ASN.1 `default` attribute: {}", e)
}));
// `extensible` attribute
} else if let Some(ext) = attr.parse_value("extensible") {
if extensible.is_some() {
abort!(attr.name, "duplicate ASN.1 `extensible` attribute");
}
extensible = Some(ext);
// `optional` attribute
} else if let Some(opt) = attr.parse_value("optional") {
if optional.is_some() {
abort!(attr.name, "duplicate ASN.1 `optional` attribute");
}
optional = Some(opt);
// `tag_mode` attribute
} else if let Some(mode) = attr.parse_value("tag_mode") {
if tag_mode.is_some() {
abort!(attr.name, "duplicate ASN.1 `tag_mode` attribute");
}
tag_mode = Some(mode);
// `type = "..."` attribute
} else if let Some(ty) = attr.parse_value("type") {
if asn1_type.is_some() {
abort!(attr.name, "duplicate ASN.1 `type` attribute: {}");
}
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,
"unknown field-level `asn1` attribute \
(valid options are `context_specific`, `type`)",
);
}
}
Self {
asn1_type,
context_specific,
default,
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<Tag> {
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`"),
},
}
}
/// Get a `der::Decoder` object which respects these field attributes.
pub fn decoder(&self) -> TokenStream {
if let Some(tag_number) = self.context_specific {
let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default();
let tag_number = tag_number.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(
reader,
#tag_number
)?
}
} else {
quote! {
match ::der::asn1::ContextSpecific::<#type_params>::decode(reader)? {
field if field.tag_number == #tag_number => Some(field),
_ => None
}
}
}
}
TagMode::Implicit => {
quote! {
::der::asn1::ContextSpecific::<#type_params>::decode_implicit(
reader,
#tag_number
)?
}
}
};
if self.is_optional() {
if let Some(default) = &self.default {
quote!(#context_specific.map(|cs| cs.value).unwrap_or_else(#default))
} else {
quote!(#context_specific.map(|cs| cs.value))
}
} else {
// TODO(tarcieri): better error handling?
let constructed = self.constructed;
quote! {
#context_specific.ok_or_else(|| {
der::Tag::ContextSpecific {
number: #tag_number,
constructed: #constructed
}.value_error()
})?.value
}
}
} else if let Some(default) = &self.default {
let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default();
self.asn1_type.map(|ty| ty.decoder()).unwrap_or_else(|| {
quote! {
Option::<#type_params>::decode(reader)?.unwrap_or_else(#default),
}
})
} else {
self.asn1_type
.map(|ty| ty.decoder())
.unwrap_or_else(|| quote!(reader.decode()?))
}
}
/// 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();
let tag_mode = self.tag_mode.to_tokens();
quote! {
::der::asn1::ContextSpecificRef {
tag_number: #tag_number,
tag_mode: #tag_mode,
value: #binding,
}.encode_value(encoder)
}
}
None => self
.asn1_type
.map(|ty| {
let encoder_obj = ty.encoder(binding);
quote!(#encoder_obj.encode_value(encoder))
})
.unwrap_or_else(|| quote!(#binding.encode_value(encoder))),
}
}
}
/// Name/value pair attribute.
struct AttrNameValue {
/// Attribute name.
pub name: Path,
/// Attribute value.
pub value: LitStr,
}
impl AttrNameValue {
/// Parse a slice of attributes.
pub fn from_attributes(attrs: &[Attribute], out: &mut Vec<Self>) {
for attr in attrs {
if !attr.path.is_ident(ATTR_NAME) {
continue;
}
let nested = match attr.parse_meta().expect(PARSE_ERR_MSG) {
Meta::List(MetaList { nested, .. }) => nested,
other => abort!(other, "malformed `asn1` attribute"),
};
for meta in &nested {
match meta {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
path,
lit: Lit::Str(lit_str),
..
})) => out.push(Self {
name: path.clone(),
value: lit_str.clone(),
}),
_ => abort!(nested, "malformed `asn1` attribute"),
}
}
}
}
/// 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
.value()
.parse()
.unwrap_or_else(|_| abort!(self.name, "error parsing attribute")),
)
} else {
None
}
}
}