14 releases
0.3.18 | Sep 12, 2022 |
---|---|
0.3.17 | Aug 19, 2022 |
0.3.14 |
|
0.3.11 | Apr 21, 2022 |
0.2.13 | Dec 22, 2021 |
#47 in #packing
6,592 downloads per month
Used in 9 crates
(via bondrewd)
320KB
4.5K
SLoC
Bondrewd
A proc-macro crate to safely and efficiently implement from_bytes/into_bytes
for structures composed of primitive types and arrays.
The major features of the crate are:
- Decode/encode big or little endian data structures
- Associated functions to
read/write
a single field instead of decoding/encoding a whole structure, saving many instructions - Ability to decode/encode C-like enums from integer types
- Pure-rust typing with attributes to assign endianness/bit-length/bit-positioning/...
- by default no failable functions are generated. Two Error types exist but are within crate features
"slice_fns"
and"hex_fns"
. - Compress structures into small amounts of bits, or use to expand large data structures across many bytes
- All generated code is
no_std
capable and 100% safe code.
Quickstart
Add the following to the dependencies
in Cargo.toml
:
[dependencies]
bondrewd = { version = "^0.1", features = ["derive"] }
bondrewd
is easily implemented on structures to implement bit-field like structures like:
use bondrewd::{BitfieldEnum, Bitfields};
///! Implement a basic CCSDS 133.0-B-2 Primary Header using rust Enums to specify fields
/// Packet Sequence Flags as per 4.1.3.4.2.2
#[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug)]
pub enum CcsdsPacketSequenceFlags {
Continuation,
Start,
End,
Unsegmented,
Invalid(u8),
}
/// CCSDS Packet version as per 4.1.3.2
#[derive(BitfieldEnum, Clone, PartialEq, Eq, Debug)]
#[bondrewd_enum(u8)]
pub enum CcsdsPacketVersion {
One,
Two,
Invalid,
}
/// Primary header object as per 4.1.3
#[derive(Bitfields, Clone, PartialEq, Eq, Debug)]
#[bondrewd(default_endianness = "be", enforce_bytes = 6)]
pub struct CcsdsPacketHeader {
#[bondrewd(enum_primitive = "u8", bit_length = 3)]
pub(crate) packet_version_number: CcsdsPacketVersion,
pub(crate) packet_type: bool,
pub(crate) sec_hdr_flag: bool,
#[bondrewd(bit_length = 11)]
pub(crate) app_process_id: u16,
#[bondrewd(enum_primitive = "u8", bit_length = 2)]
pub(crate) sequence_flags: CcsdsPacketSequenceFlags,
#[bondrewd(bit_length = 14)]
pub(crate) packet_seq_count: u16,
pub(crate) packet_data_length: u16,
}
// Now you're on your way to space :)
// Lets see what this can generate
fn main() {
let packet = CcsdsPacketHeader {
packet_version_number: CcsdsPacketVersion::Invalid,
packet_type: true,
sec_hdr_flag: true,
app_process_id: 55255 & 0b0000011111111111,
sequence_flags: CcsdsPacketSequenceFlags::Unsegmented,
packet_seq_count: 65535 & 0b0011111111111111,
packet_data_length: 65535,
};
// Turn into some bytes (clone used to assert_eq later)
let bytes = packet.clone().into_bytes();
// Play with some of the fields
match CcsdsPacketHeader::read_sequence_flags(&bytes) {
CcsdsPacketSequenceFlags::Unsegmented => println!("Unsegmented!"),
CcsdsPacketSequenceFlags::End => println!("End!"),
_ => println!("Something else")
}
// Set the secondary header flag
CcsdsPacketHeader::write_sec_hdr_flag(&mut bytes, false);
// Get back from bytes, check them
let new_packet = CcsdsPacketHeader::from_bytes(bytes);
assert_eq!(new_packet.sec_hdr_flag, false);
assert_eq!(new_packet.app_process_id, packet.app_process_id);
}
Usage Derive Details
bondrewd
implements several derive attributes for:
- Structures
- Fields
- Enums
struct
Derive features:
from_bytes
andinto_bytes
functions are created via Bitfields trait in bondrewd.- Reverse Byte Order with no runtime cost.
#[bondrewd(reverse)]
- Bit 0 positioning with
Msb0
orLsb0
with only small compile time cost.#[bondrewd(read_from = "ZERO_BIT_LOCATION")]
.ZERO_BIT_LOCATION
can bembs0
orlsb0
.
- Read functions to unpack on a per fields basis. Useful if you only need a couple fields but would rather not unpack the entire structure.
read_{field_name}()
andread_slice_{field_name}()
.
- Bit Size Enforcement. Specify how many used bits/bytes you expect the output to have.
#[bondrewd(enforce_bits = {AMOUNT_OF_BITS})]
#[bondrewd(enforce_bytes = {AMOUNT_OF_BYTES})]
#[bondrewd(enforce_full_bytes)]
field
Derive features:
- Natural typing of primitives. No Custom Type Wrapping.
#[bondrewd(bit_length = {TOTAL_BITS_TO_USE})]
#[bondrewd(byte_length = {TOTAL_BYTES_TO_USE})]
#[bondrewd(bits = "FIRST_BIT_INDEX..LAST_BIT_INDEX_PLUS_ONE")]
(To be tested).
- Enum Fields that can catch Invalid variants.
#[bondrewd(enum_primitive = "u8")]
. Currently, u8 is the only supported type, with support for more in the future.
- Inner Structures.
#[bondrewd(struct_size = {TOTAL_BYTES})]
- Per field Endianness control.
#[bondrewd(endianness = "{ENDIANNESS}")]
, ENDIANNESS can be:le
,be
,msb
,lsb
,big
,little
. use your favorite.
- Arrays.
- Element Arrays. Define the bit-length of each element in the array.
#[bondrewd(element_bit_length = {TOTAL_BITS_PER_ELEMENT})]
#[bondrewd(element_byte_length = {TOTAL_BYTES_PER_ELEMENT})]
- Block Array. Define a overall bit length. example
[u8;4]
defined with a bit-length of 28 would remove the 4 Most Significant bits.#[bondrewd(block_bit_length = {TOTAL_AMOUNT_OF_BITS})]
#[bondrewd(block_byte_length = {TOTAL_AMOUNT_OF_BYTES})]
- Element Arrays. Define the bit-length of each element in the array.
- Auto reserve fields. If the structures total bit amount is not a multiple of 8, the unused bits at the end will be ignored.
- Ignore reserve fields. read_ and read_slice_ functions are still generated but into_bytes and from_bytes will just use zeros
#[bondrewd(reserve)]
enum
Derive features:
- Derive from_primitive and into_primitive.
- Specify an
Invalid
variant for catching values that don't make sense, otherwise the last value will be used as a catch-all.#[bondrewd_enum(invalid)]
.
- Specify custom
u8
literal for discriminants on enum variants - Invalid with primitive. like the Invalid catch all above but it stores the value as a variant field.
Why Bondrewd
Historically, the main reason for the crate was to share complex data structures for Space communication protocols (e.g. CCSDS/AOS/TC/TM...) between different software services and dependencies, without performance penalties for decoding/encoding whole struct
s from bytes.
Originally, we wrote code for these formats using crates like modular_bitfield and packed_struct and shared a common crate across all software services.
For our software, we were severely constrained by compute while transferring large amounts of data, spending lots of time decoding/encoding data structures.
We found that for certain parts of the communications services that we didn't need to decode the whole structure from bytes in order to process the communications packets.
In addition, we found many times we'd just set a single field in the structure and pass the packet to the next stage.
So, to remedy these issues, we needed a bitfields implementation that allowed us the performance and safety, with the ability to only decode small parts of data to determine which node/process to pass data on to.
Both modular_bitfields/packed_struct
are great/stable libraries, and an existing, performant and correct implementation of either of these libraries would be sufficient for almost all use cases.
However, this crate looks to fill the following from these two crates:
- Read functions via associated functions. Unpack on a per field basis.
- Natural typing, using primitives directly with proc-macro attributes.
- Neither ModularBitfields or PackedStruct do this for fields that have bit lengths that are not powers of 2.
- Enum Field support that can catch invalid numbers without panics.
- N.B. PackedStruct offers this feature, but you are required to use a built-in EnumType Wrapper. ModularBitfields exhibits panic behavior.
- Reverse Byte Order with no runtime cost.
- Bit 0 positioning. Msb0 or Lsb0
- PackedStruct offers this.
- Bit Size Enforcement with non-power of 2 bit lengths.
Dependencies
~1.5MB
~36K SLoC