From eb500b9bf9777c0014e644bc6bad0db686cf79ba Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 17 Feb 2021 23:55:31 +0100 Subject: [PATCH] Slightly less hacky; two proc-macros --- Cargo.toml | 8 +- derive/Cargo.toml | 6 +- derive/src/decodable.rs | 74 +++++++++++++++++ derive/src/encodable.rs | 71 ++++++++++++++++ derive/src/lib.rs | 180 +++++++++------------------------------- src/lib.rs | 2 +- src/traits.rs | 79 ++++++------------ tests/derive.rs | 5 +- 8 files changed, 221 insertions(+), 204 deletions(-) create mode 100644 derive/src/decodable.rs create mode 100644 derive/src/encodable.rs diff --git a/Cargo.toml b/Cargo.toml index 1e447a9..058be5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,15 @@ [package] name = "simple-tlv" version = "0.1.0" -authors = ["Nicolas Stalder "] +authors = ["Nicolas Stalder ", "RustCrypto Developers"] +license = "Apache-2.0 OR MIT" edition = "2018" description = "Encoding and decoding of SIMPLE-TLV as described in ISO 7816-4, without allocations." categories = ["cryptography", "data-structures", "encoding", "no-std"] keywords = ["crypto"] readme = "README.md" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -# der = "0.2.3" -# simple-tlv_derive = { version = "0.1", optional = true, path = "derive" } simple-tlv_derive = { version = "0.1", optional = true, path = "derive" } [dependencies.heapless] @@ -20,7 +17,6 @@ version = "0.6.0" optional = true [features] -# default = ["heapless"] alloc = [] derive = ["simple-tlv_derive"] std = ["alloc"] diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 4197c85..0e6093d 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,8 +1,12 @@ [package] name = "simple-tlv_derive" version = "0.1.0" -authors = ["Nicolas Stalder "] +authors = ["Nicolas Stalder ", "RustCrypto Developers"] +license = "Apache-2.0 OR MIT" edition = "2018" +description = "Procedural macros to derive `Decodable` and `Encodable` from `simple-tlv`." +categories = ["cryptography", "data-structures", "encoding", "no-std"] +keywords = ["crypto"] [lib] proc-macro = true diff --git a/derive/src/decodable.rs b/derive/src/decodable.rs new file mode 100644 index 0000000..8bc3ddd --- /dev/null +++ b/derive/src/decodable.rs @@ -0,0 +1,74 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{Attribute, DataStruct, Field, Ident}; +use synstructure::Structure; + +use crate::{extract_tag, FieldAttrs}; + +/// Derive Decodable on a struct +pub(crate) struct DeriveDecodableStruct { + /// Field decoders + decode_fields: TokenStream, + + /// Bound fields of a struct to be returned + decode_result: TokenStream, +} + +impl DeriveDecodableStruct { + pub fn derive(s: Structure<'_>, data: &DataStruct, name: &Ident, attrs: &Vec) -> TokenStream { + + let tag = extract_tag(name, attrs); + + let mut state = Self { + decode_fields: TokenStream::new(), + decode_result: TokenStream::new(), + }; + + for field in &data.fields { + state.derive_field(field); + } + + state.finish(&s, tag) + } + + /// Derive handling for a particular `#[field(...)]` + fn derive_field(&mut self, field: &Field) { + let attrs = FieldAttrs::new(field); + self.derive_field_decoder(&attrs); + } + + /// Derive code for decoding a field of a message + fn derive_field_decoder(&mut self, field: &FieldAttrs) { + let field_name = &field.name; + let field_tag = field.tag; + let field_decoder = quote! { let #field_name = decoder.decode_tagged_value(::simple_tlv::Tag::try_from(#field_tag).unwrap())?; }; + field_decoder.to_tokens(&mut self.decode_fields); + + let field_result = quote!(#field_name,); + field_result.to_tokens(&mut self.decode_result); + } + + /// Finish deriving a struct + fn finish(self, s: &Structure<'_>, tag: u8) -> TokenStream { + + let decode_fields = self.decode_fields; + let decode_result = self.decode_result; + + s.gen_impl(quote! { + gen impl<'a> core::convert::TryFrom> for @Self { + type Error = simple_tlv::Error; + + fn try_from(tagged_slice: simple_tlv::TaggedSlice<'a>) -> simple_tlv::Result { + use core::convert::TryInto; + tagged_slice.tag().assert_eq(simple_tlv::Tag::try_from(#tag).unwrap())?; + tagged_slice.decode_nested(|decoder| { + #decode_fields + + Ok(Self { #decode_result }) + }) + } + } + }) + } +} + diff --git a/derive/src/encodable.rs b/derive/src/encodable.rs new file mode 100644 index 0000000..83a73aa --- /dev/null +++ b/derive/src/encodable.rs @@ -0,0 +1,71 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{Attribute, DataStruct, Field, Ident}; +use synstructure::Structure; + +use crate::{extract_tag, FieldAttrs}; + +/// Derive Encodable on a struct +pub(crate) struct DeriveEncodableStruct { + /// Fields of a struct to be serialized + encode_fields: TokenStream, +} + +impl DeriveEncodableStruct { + pub fn derive(s: Structure<'_>, data: &DataStruct, name: &Ident, attrs: &Vec) -> TokenStream { + + let tag = extract_tag(name, attrs); + + let mut state = Self { + encode_fields: TokenStream::new(), + }; + + for field in &data.fields { + state.derive_field(field); + } + + state.finish(&s, tag) + } + + /// Derive handling for a particular `#[field(...)]` + fn derive_field(&mut self, field: &Field) { + let attrs = FieldAttrs::new(field); + self.derive_field_encoder(&attrs); + } + + /// Derive code for encoding a field of a message + fn derive_field_encoder(&mut self, field: &FieldAttrs) { + let field_name = &field.name; + let field_tag = field.tag; + let field_encoder = quote! { &(::simple_tlv::Tag::try_from(#field_tag).unwrap().with_value(&self.#field_name)), }; + field_encoder.to_tokens(&mut self.encode_fields); + } + + /// Finish deriving a struct + fn finish(self, s: &Structure<'_>, tag: u8) -> TokenStream { + + + let encode_fields = self.encode_fields; + + s.gen_impl(quote! { + gen impl simple_tlv::Tagged for @Self { + fn tag() -> simple_tlv::Tag { + // TODO(nickray): FIXME FIXME + use core::convert::TryFrom; + simple_tlv::Tag::try_from(#tag).unwrap() + } + } + + gen impl simple_tlv::Container for @Self { + fn fields(&self, field_encoder: F) -> simple_tlv::Result + where + F: FnOnce(&[&dyn simple_tlv::Encodable]) -> simple_tlv::Result, + { + use core::convert::TryFrom; + field_encoder(&[#encode_fields]) + } + } + }) + } +} + diff --git a/derive/src/lib.rs b/derive/src/lib.rs index c40e5c9..8e68e3d 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -3,161 +3,64 @@ #![crate_type = "proc-macro"] #![warn(rust_2018_idioms, trivial_casts, unused_qualifications)] +mod decodable; +use decodable::DeriveDecodableStruct; +mod encodable; +use encodable::DeriveEncodableStruct; + + use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; use syn::{ - Attribute, DataStruct, Field, Generics, Ident, Lifetime, Lit, Meta, MetaList, MetaNameValue, NestedMeta, + Attribute, Field, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, }; use synstructure::{decl_derive, Structure}; decl_derive!( - [UntaggedCollection, attributes(tlv)] => + [Decodable, attributes(tlv)] => - /// Derive the `Message` trait. + /// Derive the [`Decodable`][1] trait on a struct. /// - /// This custom derive macro can be used to automatically impl the - /// `Message` trait for any struct representing a message which is - /// encoded as an ASN.1 `SEQUENCE`. + /// See [toplevel documentation for the `simple-tlv_derive` crate][2] for more + /// information about how to use this macro. /// - /// # `#[asn1(type = "...")]` attribute - /// - /// Placing this attribute on fields of a struct makes it possible to - /// decode types which don't directly implement the `Decode` and `Encode` - /// traits but do impl `TryInto` and `From` for one of the ASN.1 types - /// listed below: - /// - /// - `bit-string`: performs an intermediate conversion to `der::BitString` - /// - `octet-string`: performs an intermediate conversion to `der::OctetString` - /// - `printable-string`: performs an intermediate conversion to `der::PrintableString` - /// - `utf8-string`: performs an intermediate conversion to `der::Utf8String` - /// - /// Note: please open a GitHub Issue if you would like to request support - /// for additional ASN.1 types. - derive_simple_tlv + /// [1]: https://docs.rs/simple-tlv/latest/simple_tlv/trait.Decodable.html + /// [2]: https://docs.rs/simple-tlv_derive/ + derive_decodable ); -/// Custom derive for `der::Message` -fn derive_simple_tlv(s: Structure<'_>) -> TokenStream { +decl_derive!( + [Encodable, attributes(tlv)] => + + /// Derive the [`Encodable`][1] trait on a struct. + /// + /// See [toplevel documentation for the `simple-tlv_derive` crate][2] for more + /// information about how to use this macro. + /// + /// [1]: https://docs.rs/simple-tlv/latest/simple_tlv/trait.Decodable.html + /// [2]: https://docs.rs/simple-tlv_derive/ + derive_encodable +); + +/// Custom derive for `simple_tlv::Decodable` +fn derive_decodable(s: Structure<'_>) -> TokenStream { let ast = s.ast(); - // TODO(tarcieri/nickray): enum support + // TODO: enum support match &ast.data { - syn::Data::Struct(data) => DeriveStruct::derive(s, data, &ast.ident, &ast.attrs, &ast.generics), - other => panic!("can't derive `Message` on: {:?}", other), + syn::Data::Struct(data) => DeriveDecodableStruct::derive(s, data, &ast.ident, &ast.attrs), + other => panic!("can't derive `Decodable` on: {:?}", other), } } -/// Derive stuff on a struct -struct DeriveStruct { - /// Field decoders - decode_fields: TokenStream, +/// Custom derive for `simple_tlv::Encodable` +fn derive_encodable(s: Structure<'_>) -> TokenStream { + let ast = s.ast(); - /// Bound fields of a struct to be returned - decode_result: TokenStream, - - /// Fields of a struct to be serialized - encode_fields: TokenStream, -} - -impl DeriveStruct { - pub fn derive(s: Structure<'_>, data: &DataStruct, name: &Ident, attrs: &Vec, generics: &Generics) -> TokenStream { - - let tag = extract_tag(name, attrs); - - let mut state = Self { - decode_fields: TokenStream::new(), - decode_result: TokenStream::new(), - encode_fields: TokenStream::new(), - }; - - for field in &data.fields { - state.derive_field(field); - } - - state.finish(&s, tag, generics) + // TODO: enum support + match &ast.data { + syn::Data::Struct(data) => DeriveEncodableStruct::derive(s, data, &ast.ident, &ast.attrs), + other => panic!("can't derive `Encodable` on: {:?}", other), } - - /// Derive handling for a particular `#[field(...)]` - fn derive_field(&mut self, field: &Field) { - let attrs = FieldAttrs::new(field); - self.derive_field_decoder(&attrs); - self.derive_field_encoder(&attrs); - } - - /// Derive code for decoding a field of a message - fn derive_field_decoder(&mut self, field: &FieldAttrs) { - let field_name = &field.name; - let field_tag = field.tag; - let field_decoder = quote! { let #field_name = decoder.decode_tagged_value(::simple_tlv::Tag::try_from(#field_tag).unwrap())?; }; - field_decoder.to_tokens(&mut self.decode_fields); - - let field_result = quote!(#field_name,); - field_result.to_tokens(&mut self.decode_result); - } - - /// Derive code for encoding a field of a message - fn derive_field_encoder(&mut self, field: &FieldAttrs) { - let field_name = &field.name; - let field_tag = field.tag; - let field_encoder = quote! { &(::simple_tlv::Tag::try_from(#field_tag).unwrap().with_value(&self.#field_name)), }; - field_encoder.to_tokens(&mut self.encode_fields); - } - - /// Finish deriving a struct - fn finish(self, s: &Structure<'_>, tag: u8, generics: &Generics) -> TokenStream { - - let lifetime = match parse_lifetime(generics) { - Some(lifetime) => quote!(#lifetime), - None => quote!('_), - }; - - let decode_fields = self.decode_fields; - let decode_result = self.decode_result; - let encode_fields = self.encode_fields; - - s.gen_impl(quote! { - gen impl simple_tlv::Tagged for @Self { - fn tag() -> simple_tlv::Tag { - // TODO(nickray): FIXME FIXME - use core::convert::TryFrom; - simple_tlv::Tag::try_from(#tag).unwrap() - } - } - - gen impl simple_tlv::Container for @Self { - fn fields(&self, field_encoder: F) -> simple_tlv::Result - where - F: FnOnce(&[&dyn simple_tlv::Encodable]) -> simple_tlv::Result, - { - use core::convert::TryFrom; - field_encoder(&[#encode_fields]) - } - } - gen impl<'a> core::convert::TryFrom> for @Self { - type Error = simple_tlv::Error; - - fn try_from(tagged_slice: simple_tlv::TaggedSlice<'a>) -> simple_tlv::Result { - use core::convert::TryInto; - tagged_slice.tag().assert_eq(simple_tlv::Tag::try_from(#tag).unwrap())?; - tagged_slice.decode_nested(|decoder| { - #decode_fields - - Ok(Self { #decode_result }) - }) - } - } - }) - } -} - -/// Parse the first lifetime of the "self" type of the custom derive -/// -/// Returns `None` if there is no first lifetime. -fn parse_lifetime(generics: &Generics) -> Option<&Lifetime> { - generics - .lifetimes() - .next() - .map(|ref lt_ref| <_ref.lifetime) } /// Attributes of a field @@ -237,8 +140,3 @@ fn extract_tag(name: &Ident, attrs: &Vec) -> u8 { panic!("SIMPLE-TLV tag missing for `{}`", name); } } - -// /// SIMPLE-TLV tags supported by the `#[tlv(tag = "...")]` attribute -// #[derive(Copy, Clone, Debug, Eq, PartialEq)] -// #[allow(clippy::enum_variant_names)] -// struct Tag(u8); diff --git a/src/lib.rs b/src/lib.rs index f71a84a..ecc8132 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ extern crate alloc; #[cfg(feature = "derive")] -pub use simple_tlv_derive::UntaggedCollection; +pub use simple_tlv_derive::{Decodable, Encodable}; #[cfg(feature = "std")] extern crate std; diff --git a/src/traits.rs b/src/traits.rs index 26ceeab..730664c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -233,62 +233,37 @@ where // } // } -impl Encodable for [u8; 2] { - fn encoded_length(&self) -> Result { - Ok(2u8.into()) - } +macro_rules! impl_array { + ($($N:literal),*) => { + $( + impl Encodable for [u8; $N] { + fn encoded_length(&self) -> Result { + Ok(($N as u8).into()) + } - /// Encode this value as SIMPLE-TLV using the provided [`Encoder`]. - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - encoder.bytes(self.as_ref()) + /// Encode this value as SIMPLE-TLV using the provided [`Encoder`]. + fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { + encoder.bytes(self.as_ref()) + } + } + + impl Decodable<'_> for [u8; $N] { + fn decode(decoder: &mut Decoder<'_>) -> Result { + use core::convert::TryInto; + let bytes: &[u8] = decoder.bytes($N as u8)?; + Ok(bytes.try_into().unwrap()) + } + } + )* } } -impl Encodable for [u8; 3] { - fn encoded_length(&self) -> Result { - Ok(3u8.into()) - } - - /// Encode this value as SIMPLE-TLV using the provided [`Encoder`]. - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - encoder.bytes(self.as_ref()) - } -} - -impl Encodable for [u8; 4] { - fn encoded_length(&self) -> Result { - Ok(4u8.into()) - } - - /// Encode this value as SIMPLE-TLV using the provided [`Encoder`]. - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - encoder.bytes(self.as_ref()) - } -} - -impl Decodable<'_> for [u8; 2] { - fn decode(decoder: &mut Decoder<'_>) -> Result { - use core::convert::TryInto; - let bytes: &[u8] = decoder.bytes(2u8)?; - Ok(bytes.try_into().unwrap()) - } -} - -impl Decodable<'_> for [u8; 3] { - fn decode(decoder: &mut Decoder<'_>) -> Result { - use core::convert::TryInto; - let bytes: &[u8] = decoder.bytes(3u8)?; - Ok(bytes.try_into().unwrap()) - } -} - -impl Decodable<'_> for [u8; 4] { - fn decode(decoder: &mut Decoder<'_>) -> Result { - use core::convert::TryInto; - let bytes: &[u8] = decoder.bytes(4u8)?; - Ok(bytes.try_into().unwrap()) - } -} +impl_array!( + 0,1,2,3,4,5,6,7,8,9, + 10,11,12,13,14,15,16,17,18,19, + 20,21,22,23,24,25,26,27,28,29, + 30,31,32 +); #[cfg(test)] mod tests { diff --git a/tests/derive.rs b/tests/derive.rs index 9329e6c..dbd8576 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -2,10 +2,9 @@ #![cfg(feature = "derive")] -use simple_tlv::{Decodable, Encodable, Encoder, UntaggedCollection}; -// use hex_literal::hex; +use simple_tlv::{Decodable, Encodable}; -#[derive(Clone, Copy, Debug, Eq, PartialEq, UntaggedCollection)] +#[derive(Clone, Copy, Debug, Decodable, Encodable, Eq, PartialEq)] #[tlv(tag = "0xAA")] struct S { #[tlv(tag = "0x11")]