Macro hex_magic::parse_struct[][src]

parse_struct!() { /* proc-macro */ }

Macro for parsing bytes from Read readers into structs with the ability to skip padding bytes.

Syntax

parse_struct!(READER => STRUCT {
    ...
    FIELD: [BINDING @] BYTE_PATTERN [=> EXPRESSION],
    ...
})

First, the macro expects a reader or an expression the result of which would be a reader. The reader is followed by => and then by a modified form of struct instantiation.

The basic syntax of struct instantiation takes the form of FIELD: BYTE_PATTERN. This will assign the read bytes ([u8; N]) to the given field if it matches the pattern. For more advanced scenarios, such as for converting the bytes to other types, bindings and expressions can be used: FIELD: BINDING @ BYTE_PATTERN => EXPRESSION. In this case, the result of EXPRESSION will be assigned to FIELD.

A special _ field is available for matching against bytes without including them in the struct. _ fields can be specified multiple times and can be used for skipping padding bytes or for matching against bytes without including them in the struct. Bindings and expressions can be used with these fields as well but expressions must evaluate to ().

Patterns can be any of:

  • [1, 2, 3, _, 5] - standard byte array patterns
  • b"byte string!" - byte strings
  • "FF00FF 00FF00" - hex strings usable with the hex! macro

Patterns can include _ but not .. wildcards since the length of the pattern is used to determine the amount of bytes to read.

Structs or enum variants with unnamed members (Item(A, B)) can be used with the Struct { 0: ..., 1: ... } syntax.

This macro returns Result containing either the resulting struct or std::io::Error if an error occurred while reading or matching the bytes. std::io::ErrorKind::InvalidData if the bytes were not matched successfully.

Example

use hex_magic::parse_struct;
use std::io::{Read, Result};

#[derive(Debug)]
struct Data {
    a: [u8; 2],
    b: u32,
}

fn main() -> Result<()> {
    let bytes = [
        0x48, 0x45, 0x58, 0x00, 0x01, 0x02, 0x00, 0xAA, 0xBB, 0xCC, 0xDD,
    ];
    let data = parse_struct!(bytes.as_ref() => Data {
        _: b"HEX",
        _: [0],
        a: [0x01, _],
        _: "00",
        b: buf @ "AABB ____" => u32::from_le_bytes(*buf),
    })?;
    println!("{:X?}", data); // Data { a: [1, 2], b: DDCCBBAA }
    Ok(())
}

Details

This macro would be parsed into a closure which is instantly called so that any potential errors caused by Read can be handled explicitly by the user.

The macro in the example above would be parsed into the following code (internal variable names prefixed with _ changed for clarity):

(|| {
    use std::convert::TryInto;
    #[allow(non_snake_case)]
    let mut _READER = bytes.as_ref();
    #[allow(non_snake_case)]
    let mut _ARRAY: [u8; 4usize] = [0; 4usize]; // length of the longest pattern
    let _: () = {
        _READER.read_exact(&mut _ARRAY[0..3usize])?;
        #[allow(non_snake_case)]
        let _BUFFER: &[u8; 3usize] = _ARRAY[0..3usize].try_into().unwrap();
        #[allow(dead_code)]
        match _BUFFER {
            [72u8, 69u8, 88u8] => (), // b"HEX"
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("expected `{}`, got `{:02X?}`", "b\"HEX\"", _BUFFER),
                ))
            }
        }
    };
    let _: () = {
        _READER.read_exact(&mut _ARRAY[0..1usize])?;
        #[allow(non_snake_case)]
        let _BUFFER: &[u8; 1usize] = _ARRAY[0..1usize].try_into().unwrap();
        #[allow(dead_code)]
        match _BUFFER {
            [0] => (),
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("expected `{}`, got `{:02X?}`", "[0]", _BUFFER),
                ))
            }
        }
    };
    let _a = {
        _READER.read_exact(&mut _ARRAY[0..2usize])?;
        #[allow(non_snake_case)]
        let _BUFFER: &[u8; 2usize] = _ARRAY[0..2usize].try_into().unwrap();
        #[allow(dead_code)]
        match _BUFFER {
            [0x01, _] => (),
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("expected `{}`, got `{:02X?}`", "[0x01, _]", _BUFFER),
                ))
            }
        }
        *_BUFFER // [u8; 2] as the result if no expression given
    };
    let _: () = {
        _READER.read_exact(&mut _ARRAY[0..1usize])?;
        #[allow(non_snake_case)]
        let _BUFFER: &[u8; 1usize] = _ARRAY[0..1usize].try_into().unwrap();
        #[allow(dead_code)]
        match _BUFFER {
            [0u8] => (),
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("expected `{}`, got `{:02X?}`", "[0x00]", _BUFFER),
                ))
            }
        }
    };
    let _b = {
        _READER.read_exact(&mut _ARRAY[0..4usize])?;
        #[allow(non_snake_case)]
        let buf: &[u8; 4usize] = _ARRAY[0..4usize].try_into().unwrap(); // assign binding
        #[allow(dead_code)]
        match buf {
            [170u8, 187u8, _, _] => (), // "AABB ____"
            _ => {
                return Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!("expected `{}`, got `{:02X?}`", "[0xAA, 0xBB, _, _]", buf),
                ))
            }
        }
        u32::from_le_bytes(*buf) // provided expression
    };
    Ok(Data { a: _a, b: _b }) // `_` fields are not included in the resulting struct
})()