Crate tls_codec_derive
source ·Expand description
Derive macros for traits in tls_codec
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.
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.
use tls_codec_derive::{TlsDeserialize, TlsSerialize, TlsSize};
#[derive(TlsDeserialize, TlsSerialize, TlsSize)]
#[repr(u16)]
enum TypeWithUnknowns {
First = 1,
Second = 2,
}
#[test]
fn type_with_unknowns() {
let incoming = [0x00u8, 0x03]; // This must be parsed into TypeWithUnknowns into an unknown
let deserialized = TypeWithUnknowns::tls_deserialize_exact(incoming);
assert!(matches!(deserialized, Err(Error::UnknownValue(3))));
}
Available attributes
with
#[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:
prefix::tls_deserialize
when derivingDeserialize
prefix::tls_serialize
when derivingSerialize
prefix::tls_serialized_len
when derivingSize
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.
use tls_codec_derive::{TlsSerialize, TlsSize};
#[derive(TlsSerialize, TlsSize)]
struct Bytes {
#[tls_codec(with = "bytes")]
values: Vec<u8>,
}
mod bytes {
use std::io::Write;
use tls_codec::{Serialize, Size, TlsByteSliceU32};
pub fn tls_serialized_len(v: &[u8]) -> usize {
TlsByteSliceU32(v).tls_serialized_len()
}
pub fn tls_serialize<W: Write>(v: &[u8], writer: &mut W) -> Result<usize, tls_codec::Error> {
TlsByteSliceU32(v).tls_serialize(writer)
}
}
discriminant
#[tls_codec(discriminant = 123)]
#[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
).
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.
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.
use tls_codec_derive::{TlsSerialize, TlsSize};
const CONST_DISCRIMINANT: u8 = 5;
#[repr(u8)]
enum TokenType {
Constant = 3,
Variant = 4,
}
#[derive(TlsSerialize, TlsSize)]
#[repr(u8)]
enum TokenImplicit {
#[tls_codec(discriminant = 5)]
Int(u32),
// This will have the discriminant 6 as it's implicitly determined
Bytes([u8; 16]),
}
#[derive(TlsSerialize, TlsSize)]
#[repr(u8)]
enum TokenExplicit {
#[tls_codec(discriminant = "TokenType::Constant")]
Constant(u32),
#[tls_codec(discriminant = "TokenType::Variant")]
Variant(Vec<u8>),
#[tls_codec(discriminant = "CONST_DISCRIMINANT")]
StaticConstant(u8),
}
skip
#[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.
use tls_codec_derive::{TlsSerialize, TlsDeserialize, TlsSize};
struct CustomStruct;
impl Default for CustomStruct {
fn default() -> Self {
CustomStruct {}
}
}
#[derive(TlsSerialize, TlsDeserialize, TlsSize)]
struct StructWithSkip {
a: u8,
#[tls_codec(skip)]
b: CustomStruct,
c: u8,
}