Skip to content

Commit

Permalink
der_derive: add proc macro for deriving ValueOrd (#206)
Browse files Browse the repository at this point in the history
Support for deriving `ValueOrd` on structs, which is used by the
`SetOf*` types to determine ordering.
  • Loading branch information
tarcieri committed Nov 12, 2021
1 parent a036a4c commit 9e08b53
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 15 deletions.
4 changes: 2 additions & 2 deletions der/derive/src/asn1_type.rs
Expand Up @@ -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)?),
}
}

Expand Down
2 changes: 1 addition & 1 deletion der/derive/src/attributes.rs
Expand Up @@ -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)?))
}
Expand Down
2 changes: 1 addition & 1 deletion der/derive/src/choice.rs
Expand Up @@ -130,7 +130,7 @@ impl DeriveChoice {
}

/// "IR" for a variant of a derived `Choice`.
pub struct ChoiceVariant {
struct ChoiceVariant {
/// Variant name.
ident: Ident,

Expand Down
27 changes: 23 additions & 4 deletions der/derive/src/lib.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -99,6 +102,7 @@ mod choice;
mod enumerated;
mod sequence;
mod tag;
mod value_ord;

use crate::{
asn1_type::Asn1Type,
Expand All @@ -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
Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
4 changes: 2 additions & 2 deletions der/derive/src/sequence.rs
Expand Up @@ -108,7 +108,7 @@ impl DeriveSequence {
}

/// "IR" for a field of a derived `Sequence`.
pub struct SequenceField {
struct SequenceField {
/// Variant name.
ident: Ident,

Expand Down Expand Up @@ -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() {
Expand Down
144 changes: 144 additions & 0 deletions 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<Lifetime>,

/// Fields of structs or enum variants.
fields: Vec<ValueField>,
}

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),
}
}
}
}
2 changes: 1 addition & 1 deletion der/src/lib.rs
Expand Up @@ -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")))]
Expand Down
8 changes: 4 additions & 4 deletions der/tests/derive.rs
Expand Up @@ -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<Any<'a>>,
}

/// 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")]
Expand All @@ -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")]
Expand Down

0 comments on commit 9e08b53

Please sign in to comment.