From e790468cf6683866382d9b2bf1bfaf6c7c33b401 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Thu, 18 Feb 2021 03:10:50 +0100 Subject: [PATCH] Implement macros for untagged collections --- .github/workflows/docs.yml | 2 +- Cargo.toml | 2 +- derive/src/decodable.rs | 42 +++++++++++++++--------- derive/src/encodable.rs | 65 +++++++++++++++++++++++++++----------- derive/src/lib.rs | 8 ++++- src/encoder.rs | 11 +++++++ tests/derive.rs | 35 +++++++++++++++++++- 7 files changed, 127 insertions(+), 38 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b4c143a..05f43e8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: profile: minimal - name: Build Documentation - run: cargo doc --no-deps && scripts/make-toplevel-index.sh + run: cargo doc --features derive && scripts/make-toplevel-index.sh - name: Deploy Docs uses: peaceiris/actions-gh-pages@v3 diff --git a/Cargo.toml b/Cargo.toml index 058be5a..74d902a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ 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"] +keywords = ["crypto", "no_std", "serialization"] readme = "README.md" [dependencies] diff --git a/derive/src/decodable.rs b/derive/src/decodable.rs index 98a8510..d9d667f 100644 --- a/derive/src/decodable.rs +++ b/derive/src/decodable.rs @@ -3,7 +3,7 @@ use quote::{quote, ToTokens}; use syn::{Attribute, DataStruct, Field, Ident}; use synstructure::Structure; -use crate::{extract_attrs, FieldAttrs}; +use crate::{extract_attrs_optional_tag, FieldAttrs}; /// Derive Decodable on a struct pub(crate) struct DeriveDecodableStruct { @@ -17,7 +17,7 @@ pub(crate) struct DeriveDecodableStruct { impl DeriveDecodableStruct { pub fn derive(s: Structure<'_>, data: &DataStruct, name: &Ident, attrs: &[Attribute]) -> TokenStream { - let (tag, _) = extract_attrs(name, attrs); + let (tag, _) = extract_attrs_optional_tag(name, attrs); let mut state = Self { decode_fields: TokenStream::new(), @@ -56,26 +56,38 @@ impl DeriveDecodableStruct { } /// Finish deriving a struct - fn finish(self, s: &Structure<'_>, tag: u8) -> TokenStream { + fn finish(self, s: &Structure<'_>, tag: Option) -> 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; + if let Some(tag) = tag { + 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 + 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 }) - }) + Ok(Self { #decode_result }) + }) + } } - } - }) + }) + } else { + s.gen_impl(quote! { + gen impl<'a> simple_tlv::Decodable<'a> for @Self { + fn decode(decoder: &mut simple_tlv::Decoder<'a>) -> simple_tlv::Result { + use core::convert::{TryFrom, TryInto}; + #decode_fields + Ok(Self { #decode_result }) + } + } + }) + } } } diff --git a/derive/src/encodable.rs b/derive/src/encodable.rs index e7f9ecc..337dfe6 100644 --- a/derive/src/encodable.rs +++ b/derive/src/encodable.rs @@ -3,7 +3,7 @@ use quote::{quote, ToTokens}; use syn::{Attribute, DataStruct, Field, Ident}; use synstructure::Structure; -use crate::{extract_attrs, FieldAttrs}; +use crate::{extract_attrs_optional_tag, FieldAttrs}; /// Derive Encodable on a struct pub(crate) struct DeriveEncodableStruct { @@ -14,7 +14,7 @@ pub(crate) struct DeriveEncodableStruct { impl DeriveEncodableStruct { pub fn derive(s: Structure<'_>, data: &DataStruct, name: &Ident, attrs: &[Attribute]) -> TokenStream { - let (tag, _) = extract_attrs(name, attrs); + let (tag, _) = extract_attrs_optional_tag(name, attrs); let mut state = Self { encode_fields: TokenStream::new(), @@ -46,30 +46,57 @@ impl DeriveEncodableStruct { } /// Finish deriving a struct - fn finish(self, s: &Structure<'_>, tag: u8) -> TokenStream { + fn finish(self, s: &Structure<'_>, tag: Option) -> 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() + if let Some(tag) = tag { + 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 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]) + } } - } - }) + }) + } else { + s.gen_impl(quote! { + 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 simple_tlv::Encodable for @Self { + fn encoded_length(&self) -> simple_tlv::Result { + use core::convert::TryFrom; + use simple_tlv::Container; + self.fields(|encodables| simple_tlv::Length::try_from(encodables)) + } + + fn encode(&self, encoder: &mut simple_tlv::Encoder<'_>) -> simple_tlv::Result<()> { + use simple_tlv::Container; + self.fields(|fields| encoder.encode_untagged_collection(fields)) + } + } + }) + } } } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index b1e85f4..968dc5f 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -95,7 +95,7 @@ impl FieldAttrs { } } -fn extract_attrs(name: &Ident, attrs: &[Attribute]) -> (u8, bool) { +fn extract_attrs_optional_tag(name: &Ident, attrs: &[Attribute]) -> (Option, bool) { let mut tag = None; let mut slice = false; @@ -150,6 +150,12 @@ fn extract_attrs(name: &Ident, attrs: &[Attribute]) -> (u8, bool) { } } + (tag, slice) +} + +fn extract_attrs(name: &Ident, attrs: &[Attribute]) -> (u8, bool) { + let (tag, slice) = extract_attrs_optional_tag(name, attrs); + if let Some(tag) = tag { (tag, slice) } else { diff --git a/src/encoder.rs b/src/encoder.rs index da5831d..ae10e94 100644 --- a/src/encoder.rs +++ b/src/encoder.rs @@ -75,6 +75,17 @@ impl<'a> Encoder<'a> { } } + /// Encode a collection of values which impl the [`Encodable`] trait under a given tag. + pub fn encode_untagged_collection(&mut self, encodables: &[&dyn Encodable]) -> Result<()> { + let expected_len = Length::try_from(encodables)?; + let mut nested_encoder = Encoder::new(self.reserve(expected_len)?); + + for encodable in encodables { + encodable.encode(&mut nested_encoder)?; + } + Ok(()) + } + /// Encode a single byte into the backing buffer. pub(crate) fn byte(&mut self, byte: u8) -> Result<()> { match self.reserve(1u8)?.first_mut() { diff --git a/tests/derive.rs b/tests/derive.rs index 8c5bacf..c0a25c1 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -22,6 +22,14 @@ struct T { x: [u8; 1234], } +#[derive(Clone, Copy, Debug, Decodable, Encodable, Eq, PartialEq)] +struct T2 { + #[tlv(tag = "0x44", slice)] + x: [u8; 1234], + #[tlv(tag = "0x55", slice)] + a: [u8; 5], +} + #[test] fn derived_reconstruct() { let s = S { x: [1,2], y: [3,4,5], z: [6,7,8,9] }; @@ -38,7 +46,6 @@ fn derived_reconstruct() { ); let s2 = S::from_bytes(encoded).unwrap(); - assert_eq!(s, s2); } @@ -63,4 +70,30 @@ fn pretty_big() { // 1234 0x44, 0xFF, 0x04, 0xD2]); assert_eq!(&encoded[8..], x); + + let t2 = T::from_bytes(encoded).unwrap(); + assert_eq!(t, t2); +} + + +#[test] +fn derive_untagged() { + let mut x = [0u8; 1234]; + for (i, x) in x.iter_mut().enumerate() { + *x = i as _; + }; + + let t = T2 { x, a: [17u8; 5] }; + + let mut buf = [0u8; 1500]; + let encoded = t.encode_to_slice(&mut buf).unwrap(); + + assert_eq!(&encoded[..4], [ + // 1234 + 0x44, 0xFF, 0x04, 0xD2]); + assert_eq!(&encoded[4..(encoded.len() - 7)], x); + assert_eq!(&encoded[(encoded.len() - 7)..], [0x55, 5, 17, 17, 17, 17, 17]); + + let t2 = T2::from_bytes(encoded).unwrap(); + assert_eq!(t, t2); }