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:
TryPackfor 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:
TryUnpackfor 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 aMarkerTypeinto 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 toTryUnpackexcept 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 allUsing 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
- Prelude module intended for blanket inclusion to make the crate easier to use.
Macros§
- impl_
try_ unpack_ for_ owned - Provides an automatic implementation of
TryUnpackwhen wrapped around an implementation ofTryOwnedUnpack. - spinel_
read - In-line proc macro for parsing spinel-formatted data fields from a byte slice iterator.
- spinel_
write - In-line proc macro for writing spinel-formatted data fields to a type
implementing
std::io::Write. - spinel_
write_ len - In-line proc macro for determining the written length of spinel-formatted data fields.
Structs§
Enums§
- Spinel
Data Wlen - Marker type used to specify data fields that are prepended with its length.
- Spinel
Uint - Marker type used to specify integers encoded with Spinel’s variable-length unsigned integer encoding.
- Unpacking
Error - Error type for unpacking operations.
Traits§
- Spinel
Fixed Len - Marker trait for types which always serialize to the same length.
- TryOwned
Unpack - Trait for unpacking only into owned types, like
Vec<u8>orString. - TryPack
- Trait implemented by data types that support being serialized to a spinel-based byte encoding.
- TryPack
As - Trait implemented by data types that support being serialized to a specific spinel-based byte encoding, based on the marker type.
- TryUnpack
- Trait for unpacking a spinel-encoded buffer to a specific type.
- TryUnpack
As - Trait for unpacking a spinel-encoded buffer to a specific type when the field type is known.
Attribute Macros§
- spinel_
packed - Attribute macro which takes a Spinel format string as an argument
and automatically defines the
TryPack/TryUnpacktraits for the given struct.