From bcaeb26c3a82951bb50967eaa07a485600eb95c7 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Wed, 17 Feb 2021 23:29:37 +0100 Subject: [PATCH] Pretty hacky derive macro --- Cargo.toml | 3 + derive/.gitignore | 2 + derive/Cargo.toml | 14 +++ derive/src/lib.rs | 244 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/traits.rs | 111 +++++++++++---------- tests/derive.rs | 38 ++++++++ 7 files changed, 361 insertions(+), 54 deletions(-) create mode 100644 derive/.gitignore create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs create mode 100644 tests/derive.rs diff --git a/Cargo.toml b/Cargo.toml index 01cfcb4..1e447a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ readme = "README.md" [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] version = "0.6.0" @@ -20,4 +22,5 @@ optional = true [features] # default = ["heapless"] alloc = [] +derive = ["simple-tlv_derive"] std = ["alloc"] diff --git a/derive/.gitignore b/derive/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/derive/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..4197c85 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "simple-tlv_derive" +version = "0.1.0" +authors = ["Nicolas Stalder "] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = "1" +synstructure = "0.12" diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..c40e5c9 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,244 @@ +//! Custom derive support for the `simple-tlv` crate + +#![crate_type = "proc-macro"] +#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)] + +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{ + Attribute, DataStruct, Field, Generics, Ident, Lifetime, Lit, Meta, MetaList, MetaNameValue, NestedMeta, +}; +use synstructure::{decl_derive, Structure}; + +decl_derive!( + [UntaggedCollection, attributes(tlv)] => + + /// Derive the `Message` trait. + /// + /// 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`. + /// + /// # `#[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 +); + +/// Custom derive for `der::Message` +fn derive_simple_tlv(s: Structure<'_>) -> TokenStream { + let ast = s.ast(); + + // TODO(tarcieri/nickray): 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), + } +} + +/// Derive stuff on a struct +struct DeriveStruct { + /// Field decoders + decode_fields: TokenStream, + + /// 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) + } + + /// 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 +#[derive(Debug)] +struct FieldAttrs { + /// Name of the field + pub name: Ident, + + /// Value of the `#[asn1(type = "...")]` attribute if provided + pub tag: u8, +} + +impl FieldAttrs { + /// Parse the attributes of a field + fn new(field: &Field) -> Self { + let name = field + .ident + .as_ref() + .cloned() + .expect("no name on struct field i.e. tuple structs unsupported"); + + let tag = extract_tag(&name, &field.attrs); + + Self { name, tag } + } +} + +fn extract_tag(name: &Ident, attrs: &Vec) -> u8 { + let mut tag = None; + + for attr in attrs { + if !attr.path.is_ident("tlv") { + continue; + } + + match attr.parse_meta().expect("error parsing `tlv` attribute") { + Meta::List(MetaList { nested, .. }) if nested.len() == 1 => { + match nested.first() { + Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { + path, + lit: Lit::Str(lit_str), + .. + }))) => { + // Parse the `type = "..."` attribute + if !path.is_ident("tag") { + panic!("unknown `tlv` attribute for field `{}`: {:?}", name, path); + } + + if tag.is_some() { + panic!("duplicate SIMPLE-TLV `tag` attribute for field: {}", name); + } + + let possibly_with_prefix = lit_str.value(); + let without_prefix = possibly_with_prefix.trim_start_matches("0x"); + let tag_value = u8::from_str_radix(without_prefix, 16).expect("tag values must be between one and 254"); + if tag_value == 0 || tag_value == 255 { + panic!("SIMPLE-TLV tags must not be zero or 255"); + } + tag = Some(tag_value); + } + other => panic!( + "malformed `tlv` attribute for field `{}`: {:?}", + name, other + ), + } + } + other => panic!( + "malformed `tlv` attribute for field `{}`: {:?}", + name, other + ), + } + } + + if let Some(tag) = tag { + tag + } else { + 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 e1b1250..f71a84a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,9 @@ #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "derive")] +pub use simple_tlv_derive::UntaggedCollection; + #[cfg(feature = "std")] extern crate std; diff --git a/src/traits.rs b/src/traits.rs index 98370e5..26ceeab 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -233,6 +233,63 @@ where // } // } +impl Encodable for [u8; 2] { + fn encoded_length(&self) -> Result { + Ok(2u8.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; 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()) + } +} + #[cfg(test)] mod tests { @@ -240,60 +297,6 @@ mod tests { use crate::{Decodable, Decoder, Encodable, Encoder, Error, Length, Result, Tag, TaggedSlice}; use super::{Taggable, Tagged, Container}; - impl Encodable for [u8; 2] { - fn encoded_length(&self) -> Result { - Ok(2u8.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; 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 { - let bytes: &[u8] = decoder.bytes(2u8)?; - Ok(bytes.try_into().unwrap()) - } - } - - impl Decodable<'_> for [u8; 3] { - fn decode(decoder: &mut Decoder<'_>) -> Result { - let bytes: &[u8] = decoder.bytes(3u8)?; - Ok(bytes.try_into().unwrap()) - } - } - - impl Decodable<'_> for [u8; 4] { - fn decode(decoder: &mut Decoder<'_>) -> Result { - let bytes: &[u8] = decoder.bytes(4u8)?; - Ok(bytes.try_into().unwrap()) - } - } - // The types [u8; 2], [u8; 3], [u8; 4] stand in here for any types for the fields // of a struct that are Decodable + Encodable. This means they can decode to/encode from // a byte slice, but also that thye can declare their encoded length. diff --git a/tests/derive.rs b/tests/derive.rs new file mode 100644 index 0000000..9329e6c --- /dev/null +++ b/tests/derive.rs @@ -0,0 +1,38 @@ +//! Tests for custom derive support + +#![cfg(feature = "derive")] + +use simple_tlv::{Decodable, Encodable, Encoder, UntaggedCollection}; +// use hex_literal::hex; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, UntaggedCollection)] +#[tlv(tag = "0xAA")] +struct S { + #[tlv(tag = "0x11")] + x: [u8; 2], + #[tlv(tag = "0x22")] + y: [u8; 3], + #[tlv(tag = "0x33")] + z: [u8; 4], +} + +#[test] +fn derived_reconstruct() { + let s = S { x: [1,2], y: [3,4,5], z: [6,7,8,9] }; + let mut buf = [0u8; 1024]; + + let encoded = s.encode_to_slice(&mut buf).unwrap(); + + assert_eq!(encoded, + &[0xAA, 15, + 0x11, 2, 1, 2, + 0x22, 3, 3, 4, 5, + 0x33, 4, 6, 7, 8, 9, + ], + ); + + let s2 = S::from_bytes(encoded).unwrap(); + + assert_eq!(s, s2); +} +