Expand description
This Rust crate allows you to easily and safely serialize/deserialize
data structures into the byte format specified in section 3
of the Spinel Protocol Documentation. This documentation assumes
some familiarity with that documentation. The behavior of this crate was
inspired by the spinel_datatype_pack
/spinel_datatype_unpack
methods
from OpenThread.
§Key Features
- Ability to deserialize to borrowed types (like
&str
) in addition to owned types. - Convenient attribute macro (
#[spinel_packed("...")]
) for automatically implementing pack/unpack traits on datatypes, including structs with borrowed references and nested Spinel data types. - Convenient in-line macros (
spinel_read!(...)
/spinel_write!(...)
) for parsing arbitrary Spinel data immediately in place without the need to define new types. - All macros check the format strings against the types being serialized/deserialized at compile-time, generating compiler errors whenever there is a mismatch.
§Packing/Serialization
Serialization is performed via two traits:
TryPack
for serializing to the natural Spinel byte format for the given type. The key method on the trait istry_pack()
.TryPackAs<MarkerType>
for serializing to one of the specific Spinel format types. Since this trait is generic, a given type can have multiple implementations of this trait for serializing to the byte format specified byMarkerType
. The key method on the trait istry_pack_as()
.
The TryPack
/TryPackAs
traits operate on types that implement std::io::Write
,
which includes Vec<u8>
and &mut&mut[u8]
.
§Unpacking/Deserialization
Deserialization is performed via three traits:
TryUnpack
for deserializing bytes into an instance of the associated typeSelf::Unpacked
(which is usuallySelf
), inferring the byte format from the type. This trait supports deserializing to both borrowed types (like&str
) and owned types (likeString
), and thus has a lifetime parameter. The key method on the trait istry_unpack()
.TryUnpackAs<MarkerType>
for deserializing a spinel field that is encoded as aMarkerType
into an instance of the associated typeSelf::Unpacked
(which is usuallySelf
). This trait supports deserializing to both borrowed types (like&str
) and owned types (likeString
), and thus has a lifetime parameter. The key method on the trait istry_unpack_as()
.TryOwnedUnpack
, which is similar toTryUnpack
except that it supports owned types only (likeString
). The key method on the trait istry_owned_unpack()
.
The TryUnpack
/TryOwnedUnpack
traits operate on byte slices (&[u8]
) and
byte slice iterators (std::slice::Iter<u8>
).
§Supported Primitive Types
This crate supports the following raw Spinel types:
Char | Name | Type | Marker Type (if different) |
---|---|---|---|
. | VOID | () | |
b | BOOL | bool | |
C | UINT8 | u8 | |
c | INT8 | i8 | |
S | UINT16 | u16 | |
s | INT16 | i16 | |
L | UINT32 | u32 | |
l | INT32 | i32 | |
i | UINT_PACKED | u32 | SpinelUint |
6 | IPv6ADDR | std::net::Ipv6Addr | |
E | EUI64 | EUI64 | |
e | EUI48 | EUI48 | |
D | DATA | &[u8] /Vec<u8> | [u8] |
d | DATA_WLEN | &[u8] /Vec<u8> | SpinelDataWlen |
U | UTF8 | &str /String | str |
The Spinel struct (t(...)
) and array (A(...)
) types are not
directly supported and will result in a compiler error if used
in a Spinel format string. However, you can emulate the behavior of
spinel structs by replacing the t(...)
with a d
and using another
Spinel datatype (like one created with the spinel_packed
attribute)
instead of &[u8]
or Vec<u8>
.
§How Format Strings Work
The macro spinel_write!(...)
can be used to directly unpack Spinel-encoded
data into individual fields, with the encoded type being defined by a
format string. This macro will parse the format string, associating each
character in the string with a specific Marker Type and field argument. For each
of the fields in the format, the macro will make a call into
TryPackAs<MarkerType>::try_pack_as(...)
for the given field’s argument.
If there is no implementation of TryPackAs<MarkerType>
for the type of
the field’s argument, a compile-time error is generated.
§Examples
Struct example:
use spinel_pack::prelude::*;
#[spinel_packed("CiiLUE")]
#[derive(Debug, Eq, PartialEq)]
pub struct SomePackedData<'a> {
foo: u8,
bar: u32,
blah: u32,
bleh: u32,
name: &'a str,
addr: spinel_pack::EUI64,
}
Packing into a new Vec
:
let data = SomePackedData {
foo: 10,
bar: 20,
blah: 30,
bleh: 40,
name: "This is a string",
addr: spinel_pack::EUI64([0,0,0,0,0,0,0,0]),
};
let packed: Vec<u8> = data.try_packed()?;
Packing into an existing array:
let data = SomePackedData {
foo: 10,
bar: 20,
blah: 30,
bleh: 40,
name: "This is a string",
addr: spinel_pack::EUI64([0,0,0,0,0,0,0,0]),
};
let mut bytes = [0u8; 500];
let length = data.try_pack(&mut &mut bytes[..])?;
Unpacking:
let bytes: &[u8] = &[0x01, 0x02, 0x03, 0xef, 0xbe, 0xad, 0xde, 0x31, 0x32, 0x33, 0x00, 0x02,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
let data = SomePackedData::try_unpack(&mut bytes.iter())?;
assert_eq!(data.foo, 1);
assert_eq!(data.bar, 2);
assert_eq!(data.blah, 3);
assert_eq!(data.bleh, 0xdeadbeef);
assert_eq!(data.name, "123");
assert_eq!(data.addr, spinel_pack::EUI64([0x02,0xff,0xff,0xff,0xff,0xff,0xff,0xff]));
Spinel packed structs can be nested:
#[spinel_packed("idU")]
#[derive(Debug, Eq, PartialEq)]
pub struct SomeNestedData<'a> {
foo: u32,
test_struct_1: SomePackedData<'a>,
name: String,
}
Each field of a struct must have an associated format type indicator:
#[spinel_packed("i")]
#[derive(Debug, Eq, PartialEq)]
pub struct SomePackedData {
foo: u32,
data: &'a [u8], // Compiler error, no format type
}
Likewise, each format type indicator must have a field:
#[spinel_packed("id")]
#[derive(Debug, Eq, PartialEq)]
pub struct SomePackedData {
foo: u32,
} // Compiler error, missing field for 'd'
#use spinel_pack::prelude::*;
#[spinel_packed("id")]
#[derive(Debug, Eq, PartialEq)]
pub struct SomePackedData; // Compiler error, no fields at all
Using spinel_write!()
:
let mut target: Vec<u8> = vec![];
spinel_write!(&mut target, "isc", 1, 2, 3)
.expect("spinel_write failed");
assert_eq!(target, vec![1u8,2u8,0u8,3u8]);
Using spinel_read!()
:
// The data to parse.
let bytes: &[u8] = &[
0x01, 0x02, 0x83, 0x03, 0xef, 0xbe, 0xad, 0xde,
0x31, 0x32, 0x33, 0x00, 0x02, 0xf1, 0xf2, 0xf3,
0xf4, 0xf5, 0xf6, 0xf7,
];
// The variables that we will place
// the parsed values into. Note that
// currently these need to be initialized
// before they can be used with `spinel_read`.
let mut foo: u8 = 0;
let mut bar: u32 = 0;
let mut blah: u32 = 0;
let mut bleh: u32 = 0;
let mut name: String = Default::default();
let mut addr: spinel_pack::EUI64 = Default::default();
// Parse the data.
spinel_read!(&mut bytes.iter(), "CiiLUE", foo, bar, blah, bleh, name, addr)
.expect("spinel_read failed");
// Verify that the variables match
// the values we expect.
assert_eq!(foo, 1);
assert_eq!(bar, 2);
assert_eq!(blah, 387);
assert_eq!(bleh, 0xdeadbeef);
assert_eq!(name, "123");
assert_eq!(addr, spinel_pack::EUI64([0x02, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7]));
Modules§
- Prelude module intended for blanket inclusion to make the crate easier to use.
Macros§
- Provides an automatic implementation of
TryUnpack
when wrapped around an implementation ofTryOwnedUnpack
. - In-line proc macro for parsing spinel-formatted data fields from a byte slice iterator.
- In-line proc macro for writing spinel-formatted data fields to a type implementing
std::io::Write
. - In-line proc macro for determining the written length of spinel-formatted data fields.
Structs§
- Data type representing a EUI48 address.
- Data type representing a EUI64 address.
Enums§
- Marker type used to specify data fields that are prepended with its length.
- Marker type used to specify integers encoded with Spinel’s variable-length unsigned integer encoding.
- Error type for unpacking operations.
Traits§
- Marker trait for types which always serialize to the same length.
- Trait for unpacking only into owned types, like
Vec<u8>
orString
. - Trait implemented by data types that support being serialized to a spinel-based byte encoding.
- Trait implemented by data types that support being serialized to a specific spinel-based byte encoding, based on the marker type.
- Trait for unpacking a spinel-encoded buffer to a specific type.
- Trait for unpacking a spinel-encoded buffer to a specific type when the field type is known.
Attribute Macros§
- Attribute macro which takes a Spinel format string as an argument and automatically defines the
TryPack
/TryUnpack
traits for the given struct.