From d8bd0b5c9cdb589c0258b104834310e48bd9d7ef Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Thu, 7 Sep 2023 15:40:41 +0200 Subject: [PATCH 01/17] feature for conditional deserialization derivation --- tls_codec/Cargo.toml | 13 ++++---- tls_codec/derive/Cargo.toml | 3 +- tls_codec/derive/src/lib.rs | 54 +++++++++++++++++++++++++++++++- tls_codec/derive/tests/decode.rs | 27 ++++++++++++++++ 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml index 6522fee0d..7d1420415 100644 --- a/tls_codec/Cargo.toml +++ b/tls_codec/Cargo.toml @@ -26,12 +26,13 @@ criterion = "0.5" regex = "1.8" [features] -default = [ "std" ] -arbitrary = [ "std", "dep:arbitrary" ] -derive = [ "tls_codec_derive" ] -serde = [ "std", "dep:serde" ] -mls = [] # In MLS variable length vectors are limited compared to QUIC. -std = [ "tls_codec_derive?/std" ] +default = ["std"] +arbitrary = ["std", "dep:arbitrary"] +derive = ["tls_codec_derive"] +serde = ["std", "dep:serde"] +mls = [] # In MLS variable length vectors are limited compared to QUIC. +std = ["tls_codec_derive?/std"] +verifiable_structs = ["std", "derive", "tls_codec_derive/verifiable_structs"] [[bench]] name = "tls_vec" diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 6e3c10396..47fc1186b 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -23,5 +23,6 @@ tls_codec = { path = "../" } trybuild = "1" [features] -default = [ "std" ] +default = ["std"] +verifiable_structs = ["std"] std = [] diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index dc5c9d417..b8a6ef45f 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -164,13 +164,37 @@ //! c: u8, //! } //! ``` +//! +//! +#[cfg_attr( + feature = "verifiable_structs", + doc = r##" +## Verifiable structs + +```compile_fail +use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize}; + +#[derive(TlsDeserialize, TlsSerialize, TlsSize)] +struct VerifiableStruct { + pub a: u16, +} +impl VerifiableStruct { + #[cfg(feature = "verifiable_structs")] + fn deserialize(mut bytes: &[u8]) -> Result { + Self::tls_deserialize(&mut bytes) + } +} + +``` +"## +)] extern crate proc_macro; extern crate proc_macro2; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{ self, parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Data, DeriveInput, Expr, ExprLit, ExprPath, Field, Generics, Ident, Lit, Member, Meta, Result, Token, Type, @@ -512,6 +536,16 @@ pub fn deserialize_macro_derive(input: TokenStream) -> TokenStream { impl_deserialize(parsed_ast).into() } +#[proc_macro_derive(TlsDeserializeUnverified, attributes(tls_codec))] +pub fn deserialize_unverified_macro_derive(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let parsed_ast = match parse_ast(ast) { + Ok(ast) => ast, + Err(err_ts) => return err_ts.into_compile_error().into(), + }; + impl_deserialize(parsed_ast).into() +} + #[proc_macro_derive(TlsDeserializeBytes, attributes(tls_codec))] pub fn deserialize_bytes_macro_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); @@ -914,6 +948,24 @@ fn impl_deserialize(parsed_ast: TlsStruct) -> TokenStream2 { .map(|p| p.for_trait("Deserialize")) .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + // If the struct is a VERIFIABLE pattern struct, only derive Deserialize for the unverified version + let (ty_generics, impl_generics) = if cfg!(feature = "verifiable_structs") + && ty_generics.clone().to_token_stream().to_string() == "< VERIFIED >" + { + ( + quote! { + + }, + TokenStream2::new(), + ) + } else { + ( + quote! { + #ty_generics + }, + impl_generics.to_token_stream(), + ) + }; quote! { impl #impl_generics tls_codec::Deserialize for #ident #ty_generics #where_clause { #[cfg(feature = "std")] diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index 107624e23..900b4003c 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -532,3 +532,30 @@ fn type_with_unknowns() { let deserialized = TypeWithUnknowns::tls_deserialize_exact(incoming); assert!(matches!(deserialized, Err(Error::UnknownValue(3)))); } + +#[derive(TlsDeserialize, TlsSerialize, TlsSize)] +struct VerifiableStruct { + pub a: u16, +} + +impl VerifiableStruct { + fn deserialize(mut bytes: &[u8]) -> Result { + Self::tls_deserialize(&mut bytes) + } +} + +#[test] +fn verifiable_struct() { + let verifiable_struct = VerifiableStruct:: { a: 1 }; + let serialized = verifiable_struct.tls_serialize_detached().unwrap(); + let deserialized = VerifiableStruct::::tls_deserialize(&mut serialized.as_slice()); + assert!(deserialized.is_ok()); + + // Remove this to test that the verifiable struct is not deserializable when + // the const generic is set to true. + #[cfg(not(feature = "verifiable_structs"))] + { + let deserialized = VerifiableStruct::::tls_deserialize(&mut serialized.as_slice()); + assert!(deserialized.is_ok()); + } +} From b804dfdbdf42efa2a778891be3ff1ff4db9d8167 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Thu, 7 Sep 2023 15:54:46 +0200 Subject: [PATCH 02/17] fix non-std build --- tls_codec/Cargo.toml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml index 7d1420415..bf40d7dd2 100644 --- a/tls_codec/Cargo.toml +++ b/tls_codec/Cargo.toml @@ -30,9 +30,14 @@ default = ["std"] arbitrary = ["std", "dep:arbitrary"] derive = ["tls_codec_derive"] serde = ["std", "dep:serde"] -mls = [] # In MLS variable length vectors are limited compared to QUIC. +mls = [] # In MLS variable length vectors are limited compared to QUIC. std = ["tls_codec_derive?/std"] -verifiable_structs = ["std", "derive", "tls_codec_derive/verifiable_structs"] +verifiable_structs = [ + "std", + "derive", + "tls_codec_derive/verifiable_structs", + "tls_codec_derive/std", +] [[bench]] name = "tls_vec" From 478180d261312a8bb211ab49c12fe42a08cdb0e3 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Fri, 22 Sep 2023 13:48:19 +0200 Subject: [PATCH 03/17] add verifiable attribute macro --- tls_codec/derive/Cargo.toml | 2 +- tls_codec/derive/src/lib.rs | 140 ++++++++++++++++++++++++++----- tls_codec/derive/tests/decode.rs | 55 +++++++----- 3 files changed, 154 insertions(+), 43 deletions(-) diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 47fc1186b..9126d816e 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -24,5 +24,5 @@ trybuild = "1" [features] default = ["std"] -verifiable_structs = ["std"] +verifiable_structs = [] std = [] diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index b8a6ef45f..c239f9189 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -194,12 +194,16 @@ extern crate proc_macro2; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; +use quote::quote; use syn::{ self, parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Data, DeriveInput, - Expr, ExprLit, ExprPath, Field, Generics, Ident, Lit, Member, Meta, Result, Token, Type, + Expr, ExprLit, ExprPath, Field, Generics, Ident, ImplGenerics, Lit, Member, Meta, Result, + Token, Type, TypeGenerics, }; +#[cfg(feature = "verifiable_structs")] +use syn::{parse_quote, ConstParam, ItemStruct}; + /// Attribute name to identify attributes to be processed by derive-macros in this crate. const ATTR_IDENT: &str = "tls_codec"; @@ -929,6 +933,26 @@ fn impl_serialize(parsed_ast: TlsStruct, svariant: SerializeVariant) -> TokenStr } } +fn restrict_verified_generic( + impl_generics: ImplGenerics, + ty_generics: TypeGenerics, + verified: bool, +) -> (TokenStream2, TokenStream2) { + let impl_generics = quote! { #impl_generics } + .to_string() + .replace(" const IS_VERIFIED : bool ", "") + .replace("<>", "") + .parse() + .unwrap(); + let verified_string = if verified { "VERIFIED" } else { "UNVERIFIED" }; + let ty_generics = quote! { #ty_generics } + .to_string() + .replace("IS_VERIFIED", verified_string) + .parse() + .unwrap(); + (impl_generics, ty_generics) +} + #[allow(unused_variables)] fn impl_deserialize(parsed_ast: TlsStruct) -> TokenStream2 { match parsed_ast { @@ -948,24 +972,9 @@ fn impl_deserialize(parsed_ast: TlsStruct) -> TokenStream2 { .map(|p| p.for_trait("Deserialize")) .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - // If the struct is a VERIFIABLE pattern struct, only derive Deserialize for the unverified version - let (ty_generics, impl_generics) = if cfg!(feature = "verifiable_structs") - && ty_generics.clone().to_token_stream().to_string() == "< VERIFIED >" - { - ( - quote! { - - }, - TokenStream2::new(), - ) - } else { - ( - quote! { - #ty_generics - }, - impl_generics.to_token_stream(), - ) - }; + #[cfg(feature = "verifiable_structs")] + let (impl_generics, ty_generics) = + restrict_verified_generic(impl_generics, ty_generics, false); quote! { impl #impl_generics tls_codec::Deserialize for #ident #ty_generics #where_clause { #[cfg(feature = "std")] @@ -1055,6 +1064,9 @@ fn impl_deserialize_bytes(parsed_ast: TlsStruct) -> TokenStream2 { .map(|p| p.for_trait("DeserializeBytes")) .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + #[cfg(feature = "verifiable_structs")] + let (impl_generics, ty_generics) = + restrict_verified_generic(impl_generics, ty_generics, false); quote! { impl #impl_generics tls_codec::DeserializeBytes for #ident #ty_generics #where_clause { fn tls_deserialize(bytes: &[u8]) -> core::result::Result<(Self, &[u8]), tls_codec::Error> { @@ -1154,3 +1166,91 @@ fn partition_skipped( (members_skip, member_prefixes_skip), ) } + +/// The `verifiable` attribute macro takes as input either `Bytes` or `Reader` +/// and does the following: +/// * Add a boolean const generic to the struct indicating if the variant of the +/// struct was verified or not (false = unverified, true = verified). +/// * Depending on the input derive either the `TlsDeserialize` or +/// `TlsDeserializeBytes` trait for the unverified variant, but not for the +/// verified variant of the struct +/// * Create type aliases for the verified and unverified variant of the struct, +/// where the alias is the name of the struct prefixed with `Verified` or +/// `Unverified` respectively. +/// +/// The `verifiable` attribute macro is only available if the `verifiable` +/// feature is enabled. +/// +#[cfg_attr( + feature = "verifiable_structs", + doc = r##" +```compile_fail +use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize}; + +#[verifiable(Bytes)] +#[derive(TlsDeserialize, TlsSerialize, TlsSize)] +struct ExampleStruct { + pub a: u16, +} + +impl VerifiableStruct { + #[cfg(feature = "verifiable_structs")] + fn deserialize(bytes: &[u8]) -> Result { + Self::tls_deserialize_exact(bytes) + } +} +``` +"## +)] +#[cfg(feature = "verifiable_structs")] +#[proc_macro_attribute] +pub fn verifiable(input: TokenStream, annotated_item: TokenStream) -> TokenStream { + let annotated_item = parse_macro_input!(annotated_item as ItemStruct); + let input = parse_macro_input!(input as Ident); + impl_verifiable(annotated_item, input).into() +} + +#[cfg(feature = "verifiable_structs")] +fn impl_verifiable(mut annotated_item: ItemStruct, input: Ident) -> TokenStream2 { + let verifiable_const_generic: ConstParam = parse_quote! {const IS_VERIFIED: bool}; + // Add the VERIFIED const generic to the struct + annotated_item + .generics + .params + .push(verifiable_const_generic.into()); + // Derive either TlsDeserialize or TlsDeserializeBytes depending on the input + let deserialize_implementation = if input == Ident::new("Bytes", Span::call_site()) { + impl_deserialize_bytes(parse_ast(annotated_item.clone().into()).unwrap()) + } else if input == Ident::new("Reader", Span::call_site()) { + impl_deserialize(parse_ast(annotated_item.clone().into()).unwrap()) + } else { + panic!("verifiable attribute macro only supports Bytes and Reader as input"); + }; + let (impl_generics, ty_generics, _) = annotated_item.generics.split_for_impl(); + let (_unverified_impl_generics, unverified_ty_generics) = + restrict_verified_generic(impl_generics.clone(), ty_generics.clone(), false); + let (_verified_impl_generics, verified_ty_generics) = + restrict_verified_generic(impl_generics.clone(), ty_generics.clone(), true); + let annotated_item_ident = annotated_item.ident.clone(); + let verified_ident = Ident::new( + &format!("Verified{}", annotated_item_ident), + Span::call_site(), + ); + let unverified_ident = Ident::new( + &format!("Unverified{}", annotated_item_ident), + Span::call_site(), + ); + let annotated_item_visibility = annotated_item.vis.clone(); + // For now, we assume that the struct doesn't + quote! { + #annotated_item + + #annotated_item_visibility const VERIFIED: bool = true; + #annotated_item_visibility const UNVERIFIED: bool = false; + + #annotated_item_visibility type #unverified_ident = #annotated_item_ident #unverified_ty_generics; + #annotated_item_visibility type #verified_ident = #annotated_item_ident #verified_ty_generics; + + #deserialize_implementation + } +} diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index 900b4003c..7217ed915 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -533,29 +533,40 @@ fn type_with_unknowns() { assert!(matches!(deserialized, Err(Error::UnknownValue(3)))); } -#[derive(TlsDeserialize, TlsSerialize, TlsSize)] -struct VerifiableStruct { - pub a: u16, -} - -impl VerifiableStruct { - fn deserialize(mut bytes: &[u8]) -> Result { - Self::tls_deserialize(&mut bytes) +#[cfg(feature = "verifiable_structs")] +mod verifiable { + use tls_codec::{Deserialize, DeserializeBytes, Serialize}; + use tls_codec_derive::{verifiable, TlsSerialize, TlsSize}; + + #[test] + fn verifiable_struct() { + #[verifiable(Reader)] + #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] + struct ExampleStruct { + a: u8, + b: u16, + } + let verified_struct = VerifiedExampleStruct { a: 1, b: 2 }; + let serialized = verified_struct.tls_serialize_detached().unwrap(); + let unverified_struct = + UnverifiedExampleStruct::tls_deserialize(&mut serialized.as_slice()).unwrap(); + assert_eq!(unverified_struct.a, verified_struct.a); + assert_eq!(unverified_struct.b, verified_struct.b); } -} -#[test] -fn verifiable_struct() { - let verifiable_struct = VerifiableStruct:: { a: 1 }; - let serialized = verifiable_struct.tls_serialize_detached().unwrap(); - let deserialized = VerifiableStruct::::tls_deserialize(&mut serialized.as_slice()); - assert!(deserialized.is_ok()); - - // Remove this to test that the verifiable struct is not deserializable when - // the const generic is set to true. - #[cfg(not(feature = "verifiable_structs"))] - { - let deserialized = VerifiableStruct::::tls_deserialize(&mut serialized.as_slice()); - assert!(deserialized.is_ok()); + #[test] + fn verifiable_struct_bytes() { + #[verifiable(Bytes)] + #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] + struct ExampleStruct { + a: u8, + b: u16, + } + let verified_struct = VerifiedExampleStruct { a: 1, b: 2 }; + let serialized = verified_struct.tls_serialize_detached().unwrap(); + let unverified_struct = + UnverifiedExampleStruct::tls_deserialize_exact(&mut &*serialized).unwrap(); + assert_eq!(unverified_struct.a, verified_struct.a); + assert_eq!(unverified_struct.b, verified_struct.b); } } From f4c74d7760e2505f39b99860eff36ad17a04bb93 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Fri, 22 Sep 2023 13:54:10 +0200 Subject: [PATCH 04/17] cleanup --- tls_codec/derive/src/lib.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index c239f9189..2fa45bc5b 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -164,31 +164,6 @@ //! c: u8, //! } //! ``` -//! -//! -#[cfg_attr( - feature = "verifiable_structs", - doc = r##" -## Verifiable structs - -```compile_fail -use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize}; - -#[derive(TlsDeserialize, TlsSerialize, TlsSize)] -struct VerifiableStruct { - pub a: u16, -} - -impl VerifiableStruct { - #[cfg(feature = "verifiable_structs")] - fn deserialize(mut bytes: &[u8]) -> Result { - Self::tls_deserialize(&mut bytes) - } -} - -``` -"## -)] extern crate proc_macro; extern crate proc_macro2; @@ -540,16 +515,6 @@ pub fn deserialize_macro_derive(input: TokenStream) -> TokenStream { impl_deserialize(parsed_ast).into() } -#[proc_macro_derive(TlsDeserializeUnverified, attributes(tls_codec))] -pub fn deserialize_unverified_macro_derive(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let parsed_ast = match parse_ast(ast) { - Ok(ast) => ast, - Err(err_ts) => return err_ts.into_compile_error().into(), - }; - impl_deserialize(parsed_ast).into() -} - #[proc_macro_derive(TlsDeserializeBytes, attributes(tls_codec))] pub fn deserialize_bytes_macro_derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); From 955e033533c291e6cef899d2b386501059ab9076 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Fri, 22 Sep 2023 13:58:30 +0200 Subject: [PATCH 05/17] feature-gate generic restriction function --- tls_codec/derive/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index 2fa45bc5b..4ecaaff51 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -898,6 +898,7 @@ fn impl_serialize(parsed_ast: TlsStruct, svariant: SerializeVariant) -> TokenStr } } +#[cfg(feature = "verifiable_structs")] fn restrict_verified_generic( impl_generics: ImplGenerics, ty_generics: TypeGenerics, From 6cb1a10abc221455dc367428298b69d5309c890c Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Fri, 22 Sep 2023 14:02:54 +0200 Subject: [PATCH 06/17] more feature-gating fixes --- tls_codec/derive/Cargo.toml | 2 +- tls_codec/derive/src/lib.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 9126d816e..47fc1186b 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -24,5 +24,5 @@ trybuild = "1" [features] default = ["std"] -verifiable_structs = [] +verifiable_structs = ["std"] std = [] diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index 4ecaaff51..b534ed07c 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -172,12 +172,11 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::{ self, parse_macro_input, punctuated::Punctuated, token::Comma, Attribute, Data, DeriveInput, - Expr, ExprLit, ExprPath, Field, Generics, Ident, ImplGenerics, Lit, Member, Meta, Result, - Token, Type, TypeGenerics, + Expr, ExprLit, ExprPath, Field, Generics, Ident, Lit, Member, Meta, Result, Token, Type, }; #[cfg(feature = "verifiable_structs")] -use syn::{parse_quote, ConstParam, ItemStruct}; +use syn::{parse_quote, ConstParam, ImplGenerics, ItemStruct, TypeGenerics}; /// Attribute name to identify attributes to be processed by derive-macros in this crate. const ATTR_IDENT: &str = "tls_codec"; From f3db4c50b80e6724eb62ce58797f7302d90440b5 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 4 Oct 2023 16:44:25 +0200 Subject: [PATCH 07/17] fix CI --- tls_codec/Cargo.toml | 9 ++------- tls_codec/derive/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml index bf40d7dd2..e8554f3fc 100644 --- a/tls_codec/Cargo.toml +++ b/tls_codec/Cargo.toml @@ -30,14 +30,9 @@ default = ["std"] arbitrary = ["std", "dep:arbitrary"] derive = ["tls_codec_derive"] serde = ["std", "dep:serde"] -mls = [] # In MLS variable length vectors are limited compared to QUIC. +mls = [] # In MLS variable length vectors are limited compared to QUIC. std = ["tls_codec_derive?/std"] -verifiable_structs = [ - "std", - "derive", - "tls_codec_derive/verifiable_structs", - "tls_codec_derive/std", -] +verifiable_structs = ["derive", "tls_codec_derive/verifiable_structs"] [[bench]] name = "tls_vec" diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 47fc1186b..9126d816e 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -24,5 +24,5 @@ trybuild = "1" [features] default = ["std"] -verifiable_structs = ["std"] +verifiable_structs = [] std = [] From 992771185a6ae601523b14d39d30a8f3d68584b5 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Thu, 2 Nov 2023 16:36:43 +0100 Subject: [PATCH 08/17] make terminology more generic --- tls_codec/Cargo.toml | 7 ++- tls_codec/derive/Cargo.toml | 2 +- tls_codec/derive/src/lib.rs | 97 ++++++++++++++++---------------- tls_codec/derive/tests/decode.rs | 46 ++++++++------- 4 files changed, 81 insertions(+), 71 deletions(-) diff --git a/tls_codec/Cargo.toml b/tls_codec/Cargo.toml index e8554f3fc..b5aec163c 100644 --- a/tls_codec/Cargo.toml +++ b/tls_codec/Cargo.toml @@ -30,9 +30,12 @@ default = ["std"] arbitrary = ["std", "dep:arbitrary"] derive = ["tls_codec_derive"] serde = ["std", "dep:serde"] -mls = [] # In MLS variable length vectors are limited compared to QUIC. +mls = [] # In MLS variable length vectors are limited compared to QUIC. std = ["tls_codec_derive?/std"] -verifiable_structs = ["derive", "tls_codec_derive/verifiable_structs"] +conditional_deserialization = [ + "derive", + "tls_codec_derive/conditional_deserialization", +] [[bench]] name = "tls_vec" diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 9126d816e..a62e8f8b0 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -24,5 +24,5 @@ trybuild = "1" [features] default = ["std"] -verifiable_structs = [] +conditional_deserialization = [] std = [] diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index b534ed07c..bf4633cfb 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -175,7 +175,7 @@ use syn::{ Expr, ExprLit, ExprPath, Field, Generics, Ident, Lit, Member, Meta, Result, Token, Type, }; -#[cfg(feature = "verifiable_structs")] +#[cfg(feature = "conditional_deserialization")] use syn::{parse_quote, ConstParam, ImplGenerics, ItemStruct, TypeGenerics}; /// Attribute name to identify attributes to be processed by derive-macros in this crate. @@ -897,22 +897,22 @@ fn impl_serialize(parsed_ast: TlsStruct, svariant: SerializeVariant) -> TokenStr } } -#[cfg(feature = "verifiable_structs")] -fn restrict_verified_generic( +#[cfg(feature = "conditional_deserialization")] +fn restrict_conditional_generic( impl_generics: ImplGenerics, ty_generics: TypeGenerics, - verified: bool, + deserializable: bool, ) -> (TokenStream2, TokenStream2) { let impl_generics = quote! { #impl_generics } .to_string() - .replace(" const IS_VERIFIED : bool ", "") + .replace(" const IS_DESERIALIZABLE : bool ", "") .replace("<>", "") .parse() .unwrap(); - let verified_string = if verified { "VERIFIED" } else { "UNVERIFIED" }; + let deserializable_string = if deserializable { "true" } else { "false" }; let ty_generics = quote! { #ty_generics } .to_string() - .replace("IS_VERIFIED", verified_string) + .replace("IS_DESERIALIZABLE", deserializable_string) .parse() .unwrap(); (impl_generics, ty_generics) @@ -937,9 +937,9 @@ fn impl_deserialize(parsed_ast: TlsStruct) -> TokenStream2 { .map(|p| p.for_trait("Deserialize")) .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - #[cfg(feature = "verifiable_structs")] + #[cfg(feature = "conditional_deserialization")] let (impl_generics, ty_generics) = - restrict_verified_generic(impl_generics, ty_generics, false); + restrict_conditional_generic(impl_generics, ty_generics, true); quote! { impl #impl_generics tls_codec::Deserialize for #ident #ty_generics #where_clause { #[cfg(feature = "std")] @@ -1029,9 +1029,9 @@ fn impl_deserialize_bytes(parsed_ast: TlsStruct) -> TokenStream2 { .map(|p| p.for_trait("DeserializeBytes")) .collect::>(); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - #[cfg(feature = "verifiable_structs")] + #[cfg(feature = "conditional_deserialization")] let (impl_generics, ty_generics) = - restrict_verified_generic(impl_generics, ty_generics, false); + restrict_conditional_generic(impl_generics, ty_generics, true); quote! { impl #impl_generics tls_codec::DeserializeBytes for #ident #ty_generics #where_clause { fn tls_deserialize(bytes: &[u8]) -> core::result::Result<(Self, &[u8]), tls_codec::Error> { @@ -1132,77 +1132,79 @@ fn partition_skipped( ) } -/// The `verifiable` attribute macro takes as input either `Bytes` or `Reader` -/// and does the following: +/// The `conditionally_deserializable` attribute macro takes as input either +/// `Bytes` or `Reader` and does the following: /// * Add a boolean const generic to the struct indicating if the variant of the -/// struct was verified or not (false = unverified, true = verified). +/// struct is deserializable or not. /// * Depending on the input derive either the `TlsDeserialize` or -/// `TlsDeserializeBytes` trait for the unverified variant, but not for the -/// verified variant of the struct -/// * Create type aliases for the verified and unverified variant of the struct, -/// where the alias is the name of the struct prefixed with `Verified` or -/// `Unverified` respectively. +/// `TlsDeserializeBytes` trait for the deserializable variant +/// * Create type aliases for the deserializable and undeserializable variant of +/// the struct, where the alias is the name of the struct prefixed with +/// `Deserializable` or `Undeserializable` respectively. /// -/// The `verifiable` attribute macro is only available if the `verifiable` -/// feature is enabled. +/// The `conditionally_deserializable` attribute macro is only available if the +/// `conditional_deserialization` feature is enabled. /// #[cfg_attr( - feature = "verifiable_structs", + feature = "conditional_deserialization", doc = r##" ```compile_fail -use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize}; +use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize, conditionally_deserializable}; -#[verifiable(Bytes)] +#[conditionally_deserializable(Bytes)] #[derive(TlsDeserialize, TlsSerialize, TlsSize)] struct ExampleStruct { pub a: u16, } -impl VerifiableStruct { - #[cfg(feature = "verifiable_structs")] - fn deserialize(bytes: &[u8]) -> Result { +impl UndeserializableExampleStruct { + #[cfg(feature = "conditional_deserialization")] + fn deserialize(bytes: &[u8]) -> Result { Self::tls_deserialize_exact(bytes) } } ``` "## )] -#[cfg(feature = "verifiable_structs")] +#[cfg(feature = "conditional_deserialization")] #[proc_macro_attribute] -pub fn verifiable(input: TokenStream, annotated_item: TokenStream) -> TokenStream { +pub fn conditionally_deserializable( + input: TokenStream, + annotated_item: TokenStream, +) -> TokenStream { let annotated_item = parse_macro_input!(annotated_item as ItemStruct); let input = parse_macro_input!(input as Ident); - impl_verifiable(annotated_item, input).into() + impl_conditionally_deserializable(annotated_item, input).into() } -#[cfg(feature = "verifiable_structs")] -fn impl_verifiable(mut annotated_item: ItemStruct, input: Ident) -> TokenStream2 { - let verifiable_const_generic: ConstParam = parse_quote! {const IS_VERIFIED: bool}; - // Add the VERIFIED const generic to the struct +#[cfg(feature = "conditional_deserialization")] +fn impl_conditionally_deserializable(mut annotated_item: ItemStruct, input: Ident) -> TokenStream2 { + let deserializable_const_generic: ConstParam = parse_quote! {const IS_DESERIALIZABLE: bool}; + // Add the DESERIALIZABLE const generic to the struct annotated_item .generics .params - .push(verifiable_const_generic.into()); + .push(deserializable_const_generic.into()); // Derive either TlsDeserialize or TlsDeserializeBytes depending on the input let deserialize_implementation = if input == Ident::new("Bytes", Span::call_site()) { impl_deserialize_bytes(parse_ast(annotated_item.clone().into()).unwrap()) } else if input == Ident::new("Reader", Span::call_site()) { impl_deserialize(parse_ast(annotated_item.clone().into()).unwrap()) } else { - panic!("verifiable attribute macro only supports Bytes and Reader as input"); + panic!("verifiable attribute macro only supports \"Bytes\" and \"Reader\" as input"); }; let (impl_generics, ty_generics, _) = annotated_item.generics.split_for_impl(); - let (_unverified_impl_generics, unverified_ty_generics) = - restrict_verified_generic(impl_generics.clone(), ty_generics.clone(), false); - let (_verified_impl_generics, verified_ty_generics) = - restrict_verified_generic(impl_generics.clone(), ty_generics.clone(), true); + let (_deserializable_impl_generics, deserializable_ty_generics) = + restrict_conditional_generic(impl_generics.clone(), ty_generics.clone(), true); + let (_undeserializable_impl_generics, undeserializable_ty_generics) = + restrict_conditional_generic(impl_generics.clone(), ty_generics.clone(), false); let annotated_item_ident = annotated_item.ident.clone(); - let verified_ident = Ident::new( - &format!("Verified{}", annotated_item_ident), + let deserializable_ident = Ident::new( + &format!("Deserializable{}", annotated_item_ident), Span::call_site(), ); - let unverified_ident = Ident::new( - &format!("Unverified{}", annotated_item_ident), + let undeserializable_ident = Ident::new( + &format!("Undeserializable{}", annotated_item_ident), Span::call_site(), ); let annotated_item_visibility = annotated_item.vis.clone(); @@ -1210,11 +1212,8 @@ fn impl_verifiable(mut annotated_item: ItemStruct, input: Ident) -> TokenStream2 quote! { #annotated_item - #annotated_item_visibility const VERIFIED: bool = true; - #annotated_item_visibility const UNVERIFIED: bool = false; - - #annotated_item_visibility type #unverified_ident = #annotated_item_ident #unverified_ty_generics; - #annotated_item_visibility type #verified_ident = #annotated_item_ident #verified_ty_generics; + #annotated_item_visibility type #undeserializable_ident = #annotated_item_ident #undeserializable_ty_generics; + #annotated_item_visibility type #deserializable_ident = #annotated_item_ident #deserializable_ty_generics; #deserialize_implementation } diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index 7217ed915..0f58f732c 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -533,40 +533,48 @@ fn type_with_unknowns() { assert!(matches!(deserialized, Err(Error::UnknownValue(3)))); } -#[cfg(feature = "verifiable_structs")] -mod verifiable { +#[cfg(feature = "conditional_deserialization")] +mod conditional_deserialization { use tls_codec::{Deserialize, DeserializeBytes, Serialize}; - use tls_codec_derive::{verifiable, TlsSerialize, TlsSize}; + use tls_codec_derive::{conditionally_deserializable, TlsSerialize, TlsSize}; #[test] - fn verifiable_struct() { - #[verifiable(Reader)] + fn conditionally_deserializable_struct() { + #[conditionally_deserializable(Reader)] #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] struct ExampleStruct { a: u8, b: u16, } - let verified_struct = VerifiedExampleStruct { a: 1, b: 2 }; - let serialized = verified_struct.tls_serialize_detached().unwrap(); - let unverified_struct = - UnverifiedExampleStruct::tls_deserialize(&mut serialized.as_slice()).unwrap(); - assert_eq!(unverified_struct.a, verified_struct.a); - assert_eq!(unverified_struct.b, verified_struct.b); + + let undeserializable_struct = UndeserializableExampleStruct { a: 1, b: 2 }; + let serialized = undeserializable_struct.tls_serialize_detached().unwrap(); + let deserializable_struct = + DeserializableExampleStruct::tls_deserialize(&mut serialized.as_slice()).unwrap(); + assert_eq!(deserializable_struct.a, undeserializable_struct.a); + assert_eq!(deserializable_struct.b, undeserializable_struct.b); + + #[conditionally_deserializable(Reader)] + #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] + struct SecondExampleStruct { + a: u8, + b: u16, + } } #[test] - fn verifiable_struct_bytes() { - #[verifiable(Bytes)] + fn conditional_deserializable_struct_bytes() { + #[conditionally_deserializable(Bytes)] #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] struct ExampleStruct { a: u8, b: u16, } - let verified_struct = VerifiedExampleStruct { a: 1, b: 2 }; - let serialized = verified_struct.tls_serialize_detached().unwrap(); - let unverified_struct = - UnverifiedExampleStruct::tls_deserialize_exact(&mut &*serialized).unwrap(); - assert_eq!(unverified_struct.a, verified_struct.a); - assert_eq!(unverified_struct.b, verified_struct.b); + let undeserializable_struct = UndeserializableExampleStruct { a: 1, b: 2 }; + let serialized = undeserializable_struct.tls_serialize_detached().unwrap(); + let deserializable_struct = + DeserializableExampleStruct::tls_deserialize_exact(&mut &*serialized).unwrap(); + assert_eq!(deserializable_struct.a, undeserializable_struct.a); + assert_eq!(deserializable_struct.b, undeserializable_struct.b); } } From 1dcdac7d3ba12200f74b8e407a208ed3c2ff6648 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Mon, 13 Nov 2023 14:30:28 +0100 Subject: [PATCH 09/17] add "full" feature to syn dependency --- tls_codec/derive/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index a62e8f8b0..25afc6891 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.60" proc-macro = true [dependencies] -syn = { version = "2", features = ["parsing"] } +syn = { version = "2", features = ["parsing", "full"] } quote = "1.0" proc-macro2 = "1.0" From afc09195fc358287205a87a10906712990a7b456 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Mon, 13 Nov 2023 14:48:55 +0100 Subject: [PATCH 10/17] add module level docs --- tls_codec/derive/src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index bf4633cfb..90c980be0 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -164,6 +164,40 @@ //! c: u8, //! } //! ``` +//! +//! ## Conditional deserialization via the `conditionally_deserializable` attribute macro +//! +//! In some cases, it can be useful to have two variants of a struct, where one +//! is deserializable and one isn't. For example, the deserializable variant of +//! the struct could represent an unverified message, where only verification +//! produces the verified variant. Further processing could then be restricted +//! to the undeserializable struct variant. +//! +//! A pattern like this can be created via the `conditionally_deserializable` +//! attribute macro (requires the `conditional_deserialization` feature flag). +//! +//! The macro takes a single argument, which is the name of the deserialize +//! trait variant (either `Reader` or `Bytes`) that should be derived for the +//! deserializable struct part. +//! +//! The macro will then add a boolean const generic to the struct and create two +//! aliases, one for the deserializable variant and one for the undeserializable +//! one. +//! +//! ``` +//! #[conditionally_deserializable(Reader)] +//! #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] +//! struct ExampleStruct { +//! a: u8, +//! b: u16, +//! } +//! +//! let undeserializable_struct = UndeserializableExampleStruct { a: 1, b: 2 }; +//! let serialized = undeserializable_struct.tls_serialize_detached().unwrap(); +//! let deserializable_struct = +//! DeserializableExampleStruct::tls_deserialize(&mut serialized.as_slice()).unwrap(); +//! ``` + extern crate proc_macro; extern crate proc_macro2; From 0cd330b5aee4b99b4673550d9bdf56cfc9e8ae07 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Tue, 14 Nov 2023 13:33:21 +0100 Subject: [PATCH 11/17] attribute macro: both deserialization variants --- tls_codec/derive/src/lib.rs | 33 ++++++++++++++------------------ tls_codec/derive/tests/decode.rs | 6 +++--- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index 9070e50c6..d855134b8 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -176,16 +176,13 @@ //! A pattern like this can be created via the `conditionally_deserializable` //! attribute macro (requires the `conditional_deserialization` feature flag). //! -//! The macro takes a single argument, which is the name of the deserialize -//! trait variant (either `Reader` or `Bytes`) that should be derived for the -//! deserializable struct part. -//! -//! The macro will then add a boolean const generic to the struct and create two -//! aliases, one for the deserializable variant and one for the undeserializable -//! one. +//! The macro adds a boolean const generic to the struct and creates two +//! aliases, one for the deserializable variant (with a "`Deserializable`" +//! prefix) and one for the undeserializable one (with an "`Undeserializable`" +//! prefix). //! //! ``` -//! #[conditionally_deserializable(Reader)] +//! #[conditionally_deserializable] //! #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] //! struct ExampleStruct { //! a: u8, @@ -1203,16 +1200,15 @@ impl UndeserializableExampleStruct { #[cfg(feature = "conditional_deserialization")] #[proc_macro_attribute] pub fn conditionally_deserializable( - input: TokenStream, + _input: TokenStream, annotated_item: TokenStream, ) -> TokenStream { let annotated_item = parse_macro_input!(annotated_item as ItemStruct); - let input = parse_macro_input!(input as Ident); - impl_conditionally_deserializable(annotated_item, input).into() + impl_conditionally_deserializable(annotated_item).into() } #[cfg(feature = "conditional_deserialization")] -fn impl_conditionally_deserializable(mut annotated_item: ItemStruct, input: Ident) -> TokenStream2 { +fn impl_conditionally_deserializable(mut annotated_item: ItemStruct) -> TokenStream2 { let deserializable_const_generic: ConstParam = parse_quote! {const IS_DESERIALIZABLE: bool}; // Add the DESERIALIZABLE const generic to the struct annotated_item @@ -1220,13 +1216,10 @@ fn impl_conditionally_deserializable(mut annotated_item: ItemStruct, input: Iden .params .push(deserializable_const_generic.into()); // Derive either TlsDeserialize or TlsDeserializeBytes depending on the input - let deserialize_implementation = if input == Ident::new("Bytes", Span::call_site()) { - impl_deserialize_bytes(parse_ast(annotated_item.clone().into()).unwrap()) - } else if input == Ident::new("Reader", Span::call_site()) { - impl_deserialize(parse_ast(annotated_item.clone().into()).unwrap()) - } else { - panic!("verifiable attribute macro only supports \"Bytes\" and \"Reader\" as input"); - }; + let deserialize_bytes_implementation = + impl_deserialize_bytes(parse_ast(annotated_item.clone().into()).unwrap()); + let deserialize_implementation = + impl_deserialize(parse_ast(annotated_item.clone().into()).unwrap()); let (impl_generics, ty_generics, _) = annotated_item.generics.split_for_impl(); let (_deserializable_impl_generics, deserializable_ty_generics) = restrict_conditional_generic(impl_generics.clone(), ty_generics.clone(), true); @@ -1250,5 +1243,7 @@ fn impl_conditionally_deserializable(mut annotated_item: ItemStruct, input: Iden #annotated_item_visibility type #deserializable_ident = #annotated_item_ident #deserializable_ty_generics; #deserialize_implementation + + #deserialize_bytes_implementation } } diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index ac9631f41..5571bb1f5 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -535,7 +535,7 @@ mod conditional_deserialization { #[test] fn conditionally_deserializable_struct() { - #[conditionally_deserializable(Reader)] + #[conditionally_deserializable] #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] struct ExampleStruct { a: u8, @@ -549,7 +549,7 @@ mod conditional_deserialization { assert_eq!(deserializable_struct.a, undeserializable_struct.a); assert_eq!(deserializable_struct.b, undeserializable_struct.b); - #[conditionally_deserializable(Reader)] + #[conditionally_deserializable] #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] struct SecondExampleStruct { a: u8, @@ -559,7 +559,7 @@ mod conditional_deserialization { #[test] fn conditional_deserializable_struct_bytes() { - #[conditionally_deserializable(Bytes)] + #[conditionally_deserializable] #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] struct ExampleStruct { a: u8, From 78432253d8b98295d3f52ad88a5ccb297ad3c25d Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Tue, 14 Nov 2023 13:58:12 +0100 Subject: [PATCH 12/17] unused import --- tls_codec/derive/tests/decode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls_codec/derive/tests/decode.rs b/tls_codec/derive/tests/decode.rs index 5571bb1f5..d0907b25c 100644 --- a/tls_codec/derive/tests/decode.rs +++ b/tls_codec/derive/tests/decode.rs @@ -530,7 +530,7 @@ fn type_with_unknowns() { #[cfg(feature = "conditional_deserialization")] mod conditional_deserialization { - use tls_codec::{Deserialize, DeserializeBytes, Serialize}; + use tls_codec::{Deserialize, Serialize}; use tls_codec_derive::{conditionally_deserializable, TlsSerialize, TlsSize}; #[test] From 4a549b40310d30ef9b2d9aa4e6342d2f0dd5410b Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 15 Nov 2023 07:51:47 +0100 Subject: [PATCH 13/17] include doc tests in GH workflow --- .github/workflows/tls_codec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tls_codec.yml b/.github/workflows/tls_codec.yml index d4142bcef..12c259b3e 100644 --- a/.github/workflows/tls_codec.yml +++ b/.github/workflows/tls_codec.yml @@ -71,3 +71,4 @@ jobs: - uses: RustCrypto/actions/cargo-hack-install@master - run: cargo hack test --feature-powerset - run: cargo hack test -p tls_codec_derive --feature-powerset --test encode\* --test decode\* + - run: cargo hack test -p tls_codec_derive --feature-powerset --doc From 8a212eadc42a02cad17684b70bbeefe35c83a392 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 15 Nov 2023 07:52:01 +0100 Subject: [PATCH 14/17] restrict "full" syn feature to cond. ser. feature --- tls_codec/derive/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tls_codec/derive/Cargo.toml b/tls_codec/derive/Cargo.toml index 25afc6891..834096147 100644 --- a/tls_codec/derive/Cargo.toml +++ b/tls_codec/derive/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.60" proc-macro = true [dependencies] -syn = { version = "2", features = ["parsing", "full"] } +syn = { version = "2", features = ["parsing"] } quote = "1.0" proc-macro2 = "1.0" @@ -24,5 +24,5 @@ trybuild = "1" [features] default = ["std"] -conditional_deserialization = [] +conditional_deserialization = ["syn/full"] std = [] From 1c23f6dc0f09407d3ae9835471954a3460464e70 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 15 Nov 2023 07:55:03 +0100 Subject: [PATCH 15/17] fix inline documentation in macro definition --- tls_codec/derive/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index d855134b8..c29dce7f1 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -1215,17 +1215,19 @@ fn impl_conditionally_deserializable(mut annotated_item: ItemStruct) -> TokenStr .generics .params .push(deserializable_const_generic.into()); - // Derive either TlsDeserialize or TlsDeserializeBytes depending on the input + // Derive both TlsDeserialize and TlsDeserializeBytes let deserialize_bytes_implementation = impl_deserialize_bytes(parse_ast(annotated_item.clone().into()).unwrap()); let deserialize_implementation = impl_deserialize(parse_ast(annotated_item.clone().into()).unwrap()); let (impl_generics, ty_generics, _) = annotated_item.generics.split_for_impl(); + // Patch generics for use by the type aliases let (_deserializable_impl_generics, deserializable_ty_generics) = restrict_conditional_generic(impl_generics.clone(), ty_generics.clone(), true); let (_undeserializable_impl_generics, undeserializable_ty_generics) = restrict_conditional_generic(impl_generics.clone(), ty_generics.clone(), false); let annotated_item_ident = annotated_item.ident.clone(); + // Create Alias Idents by adding prefixes let deserializable_ident = Ident::new( &format!("Deserializable{}", annotated_item_ident), Span::call_site(), @@ -1235,7 +1237,6 @@ fn impl_conditionally_deserializable(mut annotated_item: ItemStruct) -> TokenStr Span::call_site(), ); let annotated_item_visibility = annotated_item.vis.clone(); - // For now, we assume that the struct doesn't quote! { #annotated_item From 4845e167c6abe825876efc6c1976ef300c525d05 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 15 Nov 2023 08:46:52 +0100 Subject: [PATCH 16/17] fix module docs --- tls_codec/derive/src/lib.rs | 106 ++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index c29dce7f1..e4e936fce 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -1,21 +1,31 @@ //! # Derive macros for traits in `tls_codec` //! +//! Derive macros can be used to automatically implement the [`Serialize`], +//! [`SerializeBytes`], [`Deserialize`], [`DeserializeBytes`], and [`Size`] +//! traits for structs and enums. Note that the functions of the [`Serialize`] +//! and [`Deserialize`] traits (and thus the corresponding derive macros) +//! require the `"std"` feature to work. +//! //! ## Warning //! -//! The derive macros support deriving the `tls_codec` traits for enumerations and the resulting -//! serialized format complies with [the "variants" section of the TLS RFC](https://datatracker.ietf.org/doc/html/rfc8446#section-3.8). -//! However support is limited to enumerations that are serialized with their discriminant -//! immediately followed by the variant data. If this is not appropriate (e.g. the format requires -//! other fields between the discriminant and variant data), the `tls_codec` traits can be -//! implemented manually. +//! The derive macros support deriving the `tls_codec` traits for enumerations +//! and the resulting serialized format complies with [the "variants" section of +//! the TLS RFC](https://datatracker.ietf.org/doc/html/rfc8446#section-3.8). +//! However support is limited to enumerations that are serialized with their +//! discriminant immediately followed by the variant data. If this is not +//! appropriate (e.g. the format requires other fields between the discriminant +//! and variant data), the `tls_codec` traits can be implemented manually. //! //! ## Parsing unknown values -//! In many cases it is necessary to deserialize structs with unknown values, e.g. -//! when receiving unknown TLS extensions. -//! In this case the deserialize function returns an `Error::UnknownValue` with -//! a `u64` value of the unknown type. +//! +//! In many cases it is necessary to deserialize structs with unknown values, +//! e.g. when receiving unknown TLS extensions. In this case the deserialize +//! function returns an `Error::UnknownValue` with a `u64` value of the unknown +//! type. //! //! ``` +//! # #[cfg(feature = "std")] +//! # { //! use tls_codec_derive::{TlsDeserialize, TlsSerialize, TlsSize}; //! //! #[derive(TlsDeserialize, TlsSerialize, TlsSize)] @@ -31,27 +41,35 @@ //! let deserialized = TypeWithUnknowns::tls_deserialize_exact(incoming); //! assert!(matches!(deserialized, Err(Error::UnknownValue(3)))); //! } +//! # } //! ``` //! //! ## Available attributes //! +//! Attributes can be used to control serialization and deserialization on a +//! per-field basis. +//! //! ### with //! //! ```text //! #[tls_codec(with = "prefix")] //! ``` //! -//! This attribute may be applied to a struct field. It indicates that deriving any of the -//! `tls_codec` traits for the containing struct calls the following functions: +//! This attribute may be applied to a struct field. It indicates that deriving +//! any of the `tls_codec` traits for the containing struct calls the following +//! functions: //! - `prefix::tls_deserialize` when deriving `Deserialize` //! - `prefix::tls_serialize` when deriving `Serialize` //! - `prefix::tls_serialized_len` when deriving `Size` //! -//! `prefix` can be a path to a module, type or trait where the functions are defined. +//! `prefix` can be a path to a module, type or trait where the functions are +//! defined. //! //! Their expected signatures match the corresponding methods in the traits. //! //! ``` +//! # #[cfg(feature = "std")] +//! # { //! use tls_codec_derive::{TlsSerialize, TlsSize}; //! //! #[derive(TlsSerialize, TlsSize)] @@ -72,6 +90,7 @@ //! TlsByteSliceU32(v).tls_serialize(writer) //! } //! } +//! # } //! ``` //! //! ### discriminant @@ -81,27 +100,34 @@ //! #[tls_codec(discriminant = "path::to::const::or::enum::Variant")] //! ``` //! -//! This attribute may be applied to an enum variant to specify the discriminant to use when -//! serializing it. If all variants are units (e.g. they do not have any data), this attribute -//! must not be used and the desired discriminants should be assigned to the variants using -//! standard Rust syntax (`Variant = Discriminant`). +//! This attribute may be applied to an enum variant to specify the discriminant +//! to use when serializing it. If all variants are units (e.g. they do not have +//! any data), this attribute must not be used and the desired discriminants +//! should be assigned to the variants using standard Rust syntax (`Variant = +//! Discriminant`). //! -//! For enumerations with non-unit variants, if no variant has this attribute, the serialization -//! discriminants will start from zero. If this attribute is used on a variant and the following -//! variant does not have it, its discriminant will be equal to the previous variant discriminant -//! plus 1. This behavior is referred to as "implicit discriminants". +//! For enumerations with non-unit variants, if no variant has this attribute, +//! the serialization discriminants will start from zero. If this attribute is +//! used on a variant and the following variant does not have it, its +//! discriminant will be equal to the previous variant discriminant plus 1. This +//! behavior is referred to as "implicit discriminants". //! -//! You can also provide paths that lead to `const` definitions or enum Variants. The important -//! thing is that any of those path expressions must resolve to something that can be coerced to -//! the `#[repr(enum_repr)]` of the enum. Please note that there are checks performed at compile -//! time to check if the provided value fits within the bounds of the `enum_repr` to avoid misuse. +//! You can also provide paths that lead to `const` definitions or enum +//! Variants. The important thing is that any of those path expressions must +//! resolve to something that can be coerced to the `#[repr(enum_repr)]` of the +//! enum. Please note that there are checks performed at compile time to check +//! if the provided value fits within the bounds of the `enum_repr` to avoid +//! misuse. //! -//! Note: When using paths *once* in your enum discriminants, as we do not have enough information -//! to deduce the next implicit discriminant (the constant expressions those paths resolve is only -//! evaluated at a later compilation stage than macros), you will be forced to use explicit -//! discriminants for all the other Variants of your enum. +//! Note: When using paths *once* in your enum discriminants, as we do not have +//! enough information to deduce the next implicit discriminant (the constant +//! expressions those paths resolve is only evaluated at a later compilation +//! stage than macros), you will be forced to use explicit discriminants for all +//! the other Variants of your enum. //! //! ``` +//! # #[cfg(feature = "std")] +//! # { //! use tls_codec_derive::{TlsSerialize, TlsSize}; //! //! const CONST_DISCRIMINANT: u8 = 5; @@ -130,7 +156,7 @@ //! #[tls_codec(discriminant = "CONST_DISCRIMINANT")] //! StaticConstant(u8), //! } -//! +//! # } //! ``` //! //! ### skip @@ -139,13 +165,16 @@ //! #[tls_codec(skip)] //! ``` //! -//! This attribute may be applied to a struct field to specify that it should be skipped. Skipping -//! means that the field at hand will neither be serialized into TLS bytes nor deserialized from TLS -//! bytes. For deserialization, it is required to populate the field with a known value. Thus, when -//! `skip` is used, the field type needs to implement the [Default] trait so it can be populated -//! with a default value. +//! This attribute may be applied to a struct field to specify that it should be +//! skipped. Skipping means that the field at hand will neither be serialized +//! into TLS bytes nor deserialized from TLS bytes. For deserialization, it is +//! required to populate the field with a known value. Thus, when `skip` is +//! used, the field type needs to implement the [Default] trait so it can be +//! populated with a default value. //! //! ``` +//! # #[cfg(feature = "std")] +//! # { //! use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize}; //! //! struct CustomStruct; @@ -163,6 +192,7 @@ //! b: CustomStruct, //! c: u8, //! } +//! # } //! ``` //! //! ## Conditional deserialization via the `conditionally_deserializable` attribute macro @@ -182,6 +212,11 @@ //! prefix). //! //! ``` +//! # #[cfg(all(feature = "conditional_deserialization", feature = "std"))] +//! # { +//! use tls_codec::{Serialize, Deserialize}; +//! use tls_codec_derive::{TlsSerialize, TlsSize, conditionally_deserializable}; +//! //! #[conditionally_deserializable] //! #[derive(TlsSize, TlsSerialize, PartialEq, Debug)] //! struct ExampleStruct { @@ -193,6 +228,7 @@ //! let serialized = undeserializable_struct.tls_serialize_detached().unwrap(); //! let deserializable_struct = //! DeserializableExampleStruct::tls_deserialize(&mut serialized.as_slice()).unwrap(); +//! # } //! ``` extern crate proc_macro; From 53bb159610e856c2f1ce7d475f946fc93c0c0582 Mon Sep 17 00:00:00 2001 From: Konrad Kohbrok Date: Wed, 15 Nov 2023 08:58:41 +0100 Subject: [PATCH 17/17] fix doc links --- tls_codec/derive/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tls_codec/derive/src/lib.rs b/tls_codec/derive/src/lib.rs index e4e936fce..5966a44f2 100644 --- a/tls_codec/derive/src/lib.rs +++ b/tls_codec/derive/src/lib.rs @@ -1,10 +1,14 @@ //! # Derive macros for traits in `tls_codec` //! -//! Derive macros can be used to automatically implement the [`Serialize`], -//! [`SerializeBytes`], [`Deserialize`], [`DeserializeBytes`], and [`Size`] -//! traits for structs and enums. Note that the functions of the [`Serialize`] -//! and [`Deserialize`] traits (and thus the corresponding derive macros) -//! require the `"std"` feature to work. +//! Derive macros can be used to automatically implement the +//! [`Serialize`](../tls_codec::Serialize), +//! [`SerializeBytes`](../tls_codec::SerializeBytes), +//! [`Deserialize`](../tls_codec::Deserialize), +//! [`DeserializeBytes`](../tls_codec::DeserializeBytes), and +//! [`Size`](../tls_codec::Size) traits for structs and enums. Note that the +//! functions of the [`Serialize`](../tls_codec::Serialize) and +//! [`Deserialize`](../tls_codec::Deserialize) traits (and thus the +//! corresponding derive macros) require the `"std"` feature to work. //! //! ## Warning //!