#parser #generate #byte #binary #endian #read #simple-parse

macro simple_parse_derive

A derive macro that implements the simple_parse traits

9 unstable releases (3 breaking)

0.6.3 Mar 18, 2021
0.6.2 Mar 5, 2021
0.6.1 Dec 29, 2020
0.5.0 Dec 22, 2020
0.1.0 Jun 23, 2020

#77 in #endian


Used in simple_parse

MIT/Apache

60KB
1K SLoC

simple_parse

crates.io mio Lines of Code

simple_parse is a declarative binary stream parser that aims to generate the most efficient parsing code possible for your custom types while remaining safe.

Features Description
Single "copy" The data is read directly into it's final destination whenever possible
Built-in endianness support Annotating structs/fields with endian gives control over how numbers will be parsed
Convert back to bytes In addition to parsing arbitrary bytes, simple_parse also allows dumping structs back into binary form

If simple_parse is unable to describe your complex/non-standard binary formats, take a look at deku or binrw.

Usage

See client_server for the complete example.

use ::simple_parse::{SpRead, SpWrite};

#[derive(SpRead, SpWrite)]
pub enum Message {
    Ping,
    Pong,
    Chat(String),
    Key {
        private: Vec<u8>,
        public: Vec<u8>,
    },
    Disconnect,
}

pub fn main() {
    /* <...> */
    
    // Declare a destination buffer to use when parsing
    let mut dst: MaybeUninit<Message> = MaybeUninit::uninit();

    loop {
        // Receive & parse bytes from the socket as a `Message` using SpRead
        let msg = Message::from_reader(&mut sock, &mut dst).expect("[server] Failed to receive message");

        match msg {
            Message::Ping => {
                println!("[server] Got Ping ! Sending Pong...");
                // Respond with a Pong using SpWrite
                (Message::Pong).to_writer(&mut sock).expect("[server] Failed to send Pong");
            },
            Message::Pong => println!("[server] got pong !"),
            Message::Chat(s) => println!("[server] Received chat : '{s}'"),
            Message::Key{private, public} => println!("[server] got keys : {private:X?}:{public:X?}"),
            Message::Disconnect => break,
        }
    }

    /* <...> */
}

For more examples see : examples/

Project Goals

In vague order of priority, simple_parse aims to provide :

  1. Safety
  2. Performance
  3. Ease of use
  4. Adaptability

In other words, simple_parse will try to generate the most performant code while never compromising on safety.

Secondly, priority will be given to ease of use by providing default implementations that work well in most cases while also allowing some customisation to accomodate for binary formats we cannot control.

Advanced Usage

simple_parse provides a few ways to enhance the generate parsing code. See attributes.rs for an exhaustive list of options.

Validation

It is possible to insert validation "hooks" at any point in the parsing/writing process.

For example, BMP image headers must always start with the two first bytes being 'BM' :

#[derive(SpRead, SpWrite)]
struct BmpHeader {
    #[sp(validate = "validate_header")]
    magic: u16,
    #[sp(endian="big")]
    size: u32,
    reserved1: u16,
    reserved2: u16,
    pixel_array_offset: u32,
    // ...

This tells simple_parse to insert a call to validate_header(magic: &u16, ctx: &mut SpCtx) directly after having populated the u16 when reading and before dumping the struct as bytes when writing.

Custom Length (for TLV style)

simple_parse provides default implementations for dynamically sized types by simply prepending the number of elements (len) followed by the elements.

i.e. By default, a Vec<u8> with three values will map to :

// [len] | [len] * [elements]
[3u32][val1][val2][val3]

When parsing binary formats that dont follow this layout, you can annotate your dynamically sized field with len :

pub struct File {
    pub content_len: u16,
    pub filename: String, // Use the default prepended len
    #[sp(len="content_len")]
    pub contents: Vec<u8>, // Use an existing field as the len

The content_len field will be used to populate contents and contents.len() will be written at that offset when writing.

Custom Read/Write

When simple_parse's default reading and writing implementations are not well suited for your formats, you can override them with the reader and writer attributes.

struct BmpHeader {
    comp_bitmask: u32,
    #[sp(
        reader="BmpComp::read, comp_bitmask",
        writer="BmpComp::write",
    )]
    compression_info: BmpComp,
    //...

When reading, this will generate code like :

compression_info = BmpComp::read(comp_bitmask: &u32, src: &mut Read, ctx: &mut SpCtx)?;

And when writing :

written_sz += BmpComp::write(&self.compression_info, ctx: &mut SpCtx, dst: &mut Write)?;

License

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~2MB
~45K SLoC