- The following list of examples are listed in the order of incremental complexity.
- All examples are implemented in the form of a test, where the structure of a given complexity is
initialized
,serialized
, and thendeserialized
with the expectation that starting and resultingstructs
are identical, with a few desired exceptions. - All examples provide both
stack
&heap
serializers for refernce.
-
Both examples are identical with exception of using a
struct
withnamed fields
vs atuple
. However, they demonstrate how to use several importantbyteserde
attribute features on thestuct
of each type, namely:-
#[byteserde(replace( ... ))]
- this is afield
level attribute and it will only affectserialization
.( ... )
expression must evaluate to the same type as the field it annotates and can reference otherstruct
members by name. Usefulness of this attribute will be covered in more advanced examples but for now you just need to know that it is possible to ignore instance value and serialize a different value of same time.- Example: calling
to_serializer_stack( &WithReplace{ value: 9 } )
will produce a byte steream which contains0x03
instead of0x09
which is value of the instance#[derive(ByteSerializeStack)] struct WithReplace{ #[byteserde(replace( 3 ))] value: u8 }
- Example: calling
-
#[byteserde(endian = "be" )]
- this attribute affect bothserialization
anddeserialization
and can be used at bothstruct
andfield
level. It will affect allrust
numeric types (signed, unsigned, floating point, and integers).be
,le
,ne
stand forBig Endian
,Little Endian
, andNative Endian
(default) respectively- Example: In below calling
let ser = to_serializer_stack( &WithEndian{ be: 1, le: 2} )
will produce a byte stream0x00 0x01 0x02 0x00
with first pair bytes representingWithEndian.be
field and second pari of bytes representingWithEndian.le
field. Note, that this attribute also affectsdeserialization
, which is a good thing because it means that both pair of bytes will be correctly interpreted when callinglet x: WithEndian = from_serializer_stack(&ser)
#[derive(ByteSerializeStack, ByteDeserializeSlice)] #[byteserde(endian = "be")] struct WithEndian{ be: u16, #[byteserde(endian = "le")] le: u16, }
- Example: In below calling
-
-
Comprehensive Examples & tests
-
Just like for numerics both examples are identicalw with exception of using a
struct
withnamed fields
vs atuple
. This example expands on numerics and introduces one additionalbyteserde
attribute, namely:-
#[byteserde(deplete( ... ))]
- unlikereplace
this attribute only affectsdeserialization
by limiting the number of bytes available to the annotatedstruct
member during deserialization.( ... )
expression need to evaluate tousize
and can reference otherstruct
members by name. This is useful when the protocol has variable length strings whose length is expressed as a value of an other struct member.- Example: In below calling
let x: WithDeplete = from_serializer_stack(&ser)
will ensure that amsg
member cannot see beyond value of themsg_length
during deserialization, whose value is guaranteed to always be set tomsg.len()
during serialization#[derive(ByteSerializeStack, ByteDeserializeSlice)] struct WithDeplete{ #[byteserde(replace( msg.len() ))] msg_length: u16, #[byteserde(deplete( msg_length ))] msg: StringAscii, }
- Example: In below calling
-
ascii
types & macros are included with thebyteserde_types
crate- Types
- StringAsciiFixed - fixed length string
- CharAscii - char, one byte long
- ConstCharAscii - constant char, one byte long
- StringAscii - variable length string using
Vec<u8>
this is a greedy type since it does not know its size at compile time will consume remaining byte stream unless limited bydeplete
attribute
- Macros
- string_ascii_fixed! - generates a
StringAsciiFixed
like type but with preffered name, length, padding and alignment - char_ascii! - generates a
CharAscii
like type but with a preffered name - const_char_ascii! - generates a
ConstcharAscii
like type but with preffered name and default ascii const char.
- string_ascii_fixed! - generates a
- Types
-
-
Comprehensive Examples & tests Regular
-
Until now all of the examples relied on two key assumptions to serialize and deserialize a byte stream. These two assumptions are:
- Types have a well defined
size
in bytes required to represent them on the byte stream, less type layout alignment, and this size is know at compile time. - Where the
size
is NOT known at compile time we were able to usedeplete
attribute to preventgreedy
deserialization
- Types have a well defined
-
On the contrary
Option<T>
types have asize
that can have two different states,zero
or defined by one of two rules above. Hence to be able to deal withOption<T>
on byte streams we introduce two newbyteserde
attributes, namely:-
#[byteserde(peek( start, len ))]
- this is astruct
level attribute and us allows to peek into the byte stream andyields
a byte slice&[u8]
-
#[byteserde(eq( ... ))]
- this is afield
level attribute it allows us to define a byte slice expression which identifies specific member whose option type follows in the byte stream. -
Example: Key considerations:
- All optional elements must have a common byte or several bytes in a fixed location to differentiate it from other optional types, in this example it is one byte
u8
in first position - All Option members must be defined in a single
struct
/ section OptionalSection
member isgreedy
and must be annotated withdeplete
instruction to know when to stop deserialization#[derive(...)] struct Opt1{ #[byteserde(replace( 1 ))] id: u8, v: u16, } #[derive(...)] struct Opt2{ #[byteserde(replace( 2 ))] id: u8, v: u32, v1: u64 } // note that Opt2 only need to match on the id field and not the rest #[derive(...)] #[byteserde(peek( 0, 1 ))] // peek one byte, yields a slice `&[u8]` of len 1 struct OptionalSection{ // all members in this struct must be of Option<X> form #[byteserde(eq( &[1] ))] // if peeked value eq to this expression deserialize as Opt1 opt1: Option<Opt1>, #[byteserde(eq( &[2] ))] // if peeked value eq to this expression deserialize as Opt2 opt2: Option<Opt2>, } #[derive(...)] struct WithOption{ //... snip a number of none optional members #[byteserde(replace( optional_section.byte_len() ))] optional_section_length: u16, #[byteserde(deplete( optional_section_length ))] optional_section: OptionalSection, //... snip more none optional members }
- All optional elements must have a common byte or several bytes in a fixed location to differentiate it from other optional types, in this example it is one byte
-
- Comprehensive Examples & tests Tuple
- Please refer to the example provided for an overview but not that just like an optional section some part of the byte stream need to be able to identify which specific variant of the enum the stream should be deserialized into.