Slightly less hacky; two proc-macros

This commit is contained in:
Nicolas Stalder
2021-02-18 00:06:05 +01:00
parent bcaeb26c3a
commit eb500b9bf9
8 changed files with 221 additions and 204 deletions
+2 -6
View File
@@ -1,18 +1,15 @@
[package]
name = "simple-tlv"
version = "0.1.0"
authors = ["Nicolas Stalder <n@stalder.io>"]
authors = ["Nicolas Stalder <n@stalder.io>", "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"]
+5 -1
View File
@@ -1,8 +1,12 @@
[package]
name = "simple-tlv_derive"
version = "0.1.0"
authors = ["Nicolas Stalder <n@stalder.io>"]
authors = ["Nicolas Stalder <n@stalder.io>", "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
+74
View File
@@ -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<Attribute>) -> 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<simple_tlv::TaggedSlice<'a>> for @Self {
type Error = simple_tlv::Error;
fn try_from(tagged_slice: simple_tlv::TaggedSlice<'a>) -> simple_tlv::Result<S> {
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 })
})
}
}
})
}
}
+71
View File
@@ -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<Attribute>) -> 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<F, T>(&self, field_encoder: F) -> simple_tlv::Result<T>
where
F: FnOnce(&[&dyn simple_tlv::Encodable]) -> simple_tlv::Result<T>,
{
use core::convert::TryFrom;
field_encoder(&[#encode_fields])
}
}
})
}
}
+39 -141
View File
@@ -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<Attribute>, 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<F, T>(&self, field_encoder: F) -> simple_tlv::Result<T>
where
F: FnOnce(&[&dyn simple_tlv::Encodable]) -> simple_tlv::Result<T>,
{
use core::convert::TryFrom;
field_encoder(&[#encode_fields])
}
}
gen impl<'a> core::convert::TryFrom<simple_tlv::TaggedSlice<'a>> for @Self {
type Error = simple_tlv::Error;
fn try_from(tagged_slice: simple_tlv::TaggedSlice<'a>) -> simple_tlv::Result<S> {
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| &lt_ref.lifetime)
}
/// Attributes of a field
@@ -237,8 +140,3 @@ fn extract_tag(name: &Ident, attrs: &Vec<Attribute>) -> 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);
+1 -1
View File
@@ -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;
+27 -52
View File
@@ -233,62 +233,37 @@ where
// }
// }
impl Encodable for [u8; 2] {
fn encoded_length(&self) -> Result<Length> {
Ok(2u8.into())
}
macro_rules! impl_array {
($($N:literal),*) => {
$(
impl Encodable for [u8; $N] {
fn encoded_length(&self) -> Result<Length> {
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<Self> {
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<Length> {
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<Length> {
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<Self> {
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<Self> {
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<Self> {
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 {
+2 -3
View File
@@ -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")]