Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

der_derive: add proc macro for deriving ValueOrd #206

Merged
merged 1 commit into from Nov 12, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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