Pretty hacky derive macro

This commit is contained in:
Nicolas Stalder
2021-02-17 23:29:37 +01:00
parent 066c827104
commit bcaeb26c3a
7 changed files with 361 additions and 54 deletions
+3
View File
@@ -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"]
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "simple-tlv_derive"
version = "0.1.0"
authors = ["Nicolas Stalder <n@stalder.io>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = "1"
synstructure = "0.12"
+244
View File
@@ -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<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)
}
/// 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
#[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<Attribute>) -> 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);
+3
View File
@@ -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;
+57 -54
View File
@@ -233,6 +233,63 @@ where
// }
// }
impl Encodable for [u8; 2] {
fn encoded_length(&self) -> Result<Length> {
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<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())
}
}
#[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<Length> {
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<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> {
let bytes: &[u8] = decoder.bytes(2u8)?;
Ok(bytes.try_into().unwrap())
}
}
impl Decodable<'_> for [u8; 3] {
fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
let bytes: &[u8] = decoder.bytes(3u8)?;
Ok(bytes.try_into().unwrap())
}
}
impl Decodable<'_> for [u8; 4] {
fn decode(decoder: &mut Decoder<'_>) -> Result<Self> {
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.
+38
View File
@@ -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);
}