From 505de3c7b953a8c276e46ea25675009eecd054e4 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 12 Nov 2021 08:20:27 -0700 Subject: [PATCH] der_derive: add proc macro for deriving `ValueOrd` Support for deriving `ValueOrd` on structs, which is used by the `SetOf*` types to determine ordering. --- der/derive/src/asn1_type.rs | 4 +- der/derive/src/attributes.rs | 2 +- der/derive/src/choice.rs | 2 +- der/derive/src/lib.rs | 27 ++++++- der/derive/src/sequence.rs | 4 +- der/derive/src/value_ord.rs | 144 +++++++++++++++++++++++++++++++++++ der/src/lib.rs | 2 +- der/tests/derive.rs | 8 +- 8 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 der/derive/src/value_ord.rs diff --git a/der/derive/src/asn1_type.rs b/der/derive/src/asn1_type.rs index 9c4ed075d..cd7b5c2f4 100644 --- a/der/derive/src/asn1_type.rs +++ b/der/derive/src/asn1_type.rs @@ -65,8 +65,8 @@ impl Asn1Type { Asn1Type::Ia5String | Asn1Type::OctetString | Asn1Type::PrintableString - | Asn1Type::Utf8String => quote!(#type_path::new(#binding)), - _ => quote!(#type_path::try_from(#binding)), + | Asn1Type::Utf8String => quote!(#type_path::new(#binding)?), + _ => quote!(#type_path::try_from(#binding)?), } } diff --git a/der/derive/src/attributes.rs b/der/derive/src/attributes.rs index 1bc030699..77abfc7a9 100644 --- a/der/derive/src/attributes.rs +++ b/der/derive/src/attributes.rs @@ -191,7 +191,7 @@ impl FieldAttrs { self.asn1_type .map(|ty| { let encoder_obj = ty.encoder(binding); - quote!(#encoder_obj?.encode(encoder)) + quote!(#encoder_obj.encode(encoder)) }) .unwrap_or_else(|| quote!(encoder.encode(#binding)?)) } diff --git a/der/derive/src/choice.rs b/der/derive/src/choice.rs index 4e7d2cf61..fb3cd184c 100644 --- a/der/derive/src/choice.rs +++ b/der/derive/src/choice.rs @@ -130,7 +130,7 @@ impl DeriveChoice { } /// "IR" for a variant of a derived `Choice`. -pub struct ChoiceVariant { +struct ChoiceVariant { /// Variant name. ident: Ident, diff --git a/der/derive/src/lib.rs b/der/derive/src/lib.rs index 1d938e046..a26453301 100644 --- a/der/derive/src/lib.rs +++ b/der/derive/src/lib.rs @@ -7,10 +7,13 @@ //! following way: //! //! - [`Choice`][`derive@Choice`]: map ASN.1 `CHOICE` to a Rust enum. +//! - [`Enumerated`][`derive@Enumerated`]: map ASN.1 `ENUMERATED` to a C-like Rust enum. //! - [`Sequence`][`derive@Sequence`]: map ASN.1 `SEQUENCE` to a Rust struct. +//! - [`ValueOrd`][`derive@ValueOrd`]: determine DER ordering for ASN.1 `SET OF`. //! //! Note that this crate shouldn't be used directly, but instead accessed -//! by using the `derive` feature of the `der` crate. +//! by using the `derive` feature of the `der` crate, which re-exports the +//! above macros from the toplevel. //! //! ## Why not `serde`? //! The `der` crate is designed to be easily usable in embedded environments, @@ -99,6 +102,7 @@ mod choice; mod enumerated; mod sequence; mod tag; +mod value_ord; use crate::{ asn1_type::Asn1Type, @@ -107,12 +111,13 @@ use crate::{ enumerated::DeriveEnumerated, sequence::DeriveSequence, tag::{Tag, TagMode, TagNumber}, + value_ord::DeriveValueOrd, }; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; use syn::{parse_macro_input, DeriveInput}; -/// Derive the [`Choice`][1] trait on an enum. +/// Derive the [`Choice`][1] trait on an `enum`. /// /// This custom derive macro can be used to automatically impl the /// [`Decodable`][2] and [`Encodable`][3] traits along with the @@ -156,7 +161,8 @@ pub fn derive_choice(input: TokenStream) -> TokenStream { DeriveChoice::new(input).to_tokens().into() } -/// Derive decoders and encoders for ASN.1 [`Enumerated`] types. +/// Derive decoders and encoders for ASN.1 [`Enumerated`] types on a +/// C-like `enum` type. /// /// # Usage /// @@ -191,7 +197,7 @@ pub fn derive_enumerated(input: TokenStream) -> TokenStream { DeriveEnumerated::new(input).to_tokens().into() } -/// Derive the [`Sequence`][1] trait on a struct. +/// Derive the [`Sequence`][1] trait on a `struct`. /// /// This custom derive macro can be used to automatically impl the /// `Sequence` trait for any struct which can be decoded/encoded as an @@ -230,3 +236,16 @@ pub fn derive_sequence(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); DeriveSequence::new(input).to_tokens().into() } + +/// Derive the [`ValueOrd`][1] trait on a `struct`. +/// +/// This trait is used in conjunction with ASN.1 `SET OF` types to determine +/// the lexicographical order of their DER encodings. +/// +/// [1]: https://docs.rs/der/latest/der/trait.ValueOrd.html +#[proc_macro_derive(ValueOrd, attributes(asn1))] +#[proc_macro_error] +pub fn derive_value_ord(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + DeriveValueOrd::new(input).to_tokens().into() +} diff --git a/der/derive/src/sequence.rs b/der/derive/src/sequence.rs index 41dd87170..9fdc2c5d0 100644 --- a/der/derive/src/sequence.rs +++ b/der/derive/src/sequence.rs @@ -108,7 +108,7 @@ impl DeriveSequence { } /// "IR" for a field of a derived `Sequence`. -pub struct SequenceField { +struct SequenceField { /// Variant name. ident: Ident, @@ -186,7 +186,7 @@ impl SequenceField { if let Some(ty) = &self.attrs.asn1_type { let encoder = ty.encoder(&binding); - quote!(&#encoder?) + quote!(&#encoder) } else if let Some(default) = &self.attrs.default { quote! { &::der::asn1::OptionalRef(if #binding == &#default() { diff --git a/der/derive/src/value_ord.rs b/der/derive/src/value_ord.rs new file mode 100644 index 000000000..6b08f7ffd --- /dev/null +++ b/der/derive/src/value_ord.rs @@ -0,0 +1,144 @@ +//! Support for deriving the `ValueOrd` trait on enums and structs. +//! +//! This trait is used in conjunction with ASN.1 `SET OF` types to determine +//! the lexicographical order of their DER encodings. + +// TODO(tarcieri): enum support + +use crate::{FieldAttrs, TypeAttrs}; +use proc_macro2::TokenStream; +use proc_macro_error::abort; +use quote::quote; +use syn::{DeriveInput, Field, Ident, Lifetime, Variant}; + +/// Derive the `Enumerated` trait for an enum. +pub(crate) struct DeriveValueOrd { + /// Name of the enum. + ident: Ident, + + /// Lifetime of the struct. + lifetime: Option, + + /// Fields of structs or enum variants. + fields: Vec, +} + +impl DeriveValueOrd { + /// Parse [`DeriveInput`]. + pub fn new(input: DeriveInput) -> Self { + let ident = input.ident; + let type_attrs = TypeAttrs::parse(&input.attrs); + + // TODO(tarcieri): properly handle multiple lifetimes + let lifetime = input + .generics + .lifetimes() + .next() + .map(|lt| lt.lifetime.clone()); + + let fields = match input.data { + syn::Data::Enum(data) => data + .variants + .into_iter() + .map(|variant| ValueField::new_enum(variant, &type_attrs)) + .collect(), + syn::Data::Struct(data) => data + .fields + .into_iter() + .map(|field| ValueField::new_struct(field, &type_attrs)) + .collect(), + _ => abort!( + ident, + "can't derive `ValueOrd` on this type: \ + only `enum` and `struct` types are allowed", + ), + }; + + Self { + ident, + lifetime, + fields, + } + } + + /// Lower the derived output into a [`TokenStream`]. + pub fn to_tokens(&self) -> TokenStream { + let ident = &self.ident; + + // Lifetime parameters + // TODO(tarcieri): support multiple lifetimes + let lt_params = self + .lifetime + .as_ref() + .map(|lt| vec![lt.clone()]) + .unwrap_or_default(); + + let mut body = Vec::new(); + + for field in &self.fields { + body.push(field.to_tokens()); + } + + quote! { + impl<#(#lt_params)*> ::der::ValueOrd for #ident<#(#lt_params)*> { + fn value_cmp(&self, other: &Self) -> ::der::Result<::core::cmp::Ordering> { + #[allow(unused_imports)] + use ::der::DerOrd; + + #(#body)* + + Ok(::core::cmp::Ordering::Equal) + } + } + } + } +} + +struct ValueField { + /// Name of the field + ident: Ident, + + /// Field-level attributes. + attrs: FieldAttrs, +} + +impl ValueField { + /// Create from an `enum` variant. + fn new_enum(variant: Variant, _: &TypeAttrs) -> Self { + abort!( + variant, + "deriving `ValueOrd` only presently supported for structs" + ); + } + + /// Create from a `struct` field. + fn new_struct(field: Field, type_attrs: &TypeAttrs) -> Self { + let ident = field + .ident + .as_ref() + .cloned() + .unwrap_or_else(|| abort!(&field, "tuple structs are not supported")); + + let attrs = FieldAttrs::parse(&field.attrs, type_attrs); + Self { ident, attrs } + } + + /// Lower to [`TokenStream`]. + fn to_tokens(&self) -> TokenStream { + let ident = &self.ident; + let mut binding1 = quote!(self.#ident); + let mut binding2 = quote!(other.#ident); + + if let Some(ty) = &self.attrs.asn1_type { + binding1 = ty.encoder(&binding1); + binding2 = ty.encoder(&binding2); + } + + quote! { + match #binding1.der_cmp(&#binding2)? { + ::core::cmp::Ordering::Equal => (), + other => return Ok(other), + } + } + } +} diff --git a/der/src/lib.rs b/der/src/lib.rs index 51edfd31b..177d248af 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -375,7 +375,7 @@ pub use crypto_bigint as bigint; #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] -pub use der_derive::{Choice, Enumerated, Sequence}; +pub use der_derive::{Choice, Enumerated, Sequence, ValueOrd}; #[cfg(feature = "pem")] #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] diff --git a/der/tests/derive.rs b/der/tests/derive.rs index c1c6d75ee..27d155f58 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -202,19 +202,19 @@ mod enumerated { mod sequence { use der::{ asn1::{Any, ObjectIdentifier}, - Decodable, Encodable, Sequence, + Decodable, Encodable, Sequence, ValueOrd, }; use hex_literal::hex; /// X.509 `AlgorithmIdentifier` - #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] pub struct AlgorithmIdentifier<'a> { pub algorithm: ObjectIdentifier, pub parameters: Option>, } /// X.509 `SubjectPublicKeyInfo` (SPKI) - #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] pub struct SubjectPublicKeyInfo<'a> { pub algorithm: AlgorithmIdentifier<'a>, #[asn1(type = "BIT STRING")] @@ -223,7 +223,7 @@ mod sequence { /// X.509 extension // TODO(tarcieri): tests for code derived with the `default` attribute - #[derive(Clone, Debug, Eq, PartialEq, Sequence)] + #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] pub struct Extension<'a> { extn_id: ObjectIdentifier, #[asn1(default = "critical_default")]