Hi everyone.
First of all I am looking for some idea for inspiration. But if you could provide some code or recipe this will be also very cool.
What I am working on: I want to replace my own de-/serialization proc-macro with binrw
crate since the last is much better. This is my project and I am refactoring its UI part.
What I want to achieve: I need to collect metadata for each field of binrw
struct, this metadata should contain offset from the beginning of the byte-array (which I use for serialization) and the actual size of the field (the amount of consumed bytes).
Why I need this: I am working on the component where I want to highlight the part of the packet hex string according to the field (something similar you can find in the wireshark dump output). So I need the data for proper calculation of which part of the hex string I need to highlight.
What kind of issue have I faced with: I have a macro where I want to implement the logic of collecting the metadata and providing some methods to each struct:
#[macro_export]
macro_rules! make_packet {
(
$(#[$attrs:meta])*
struct $struct_name:ident {
$(
$(#[$field_attr:meta])*
$field_name:ident: $field_type: ty,
)*
}
) => {
mod packet {
use std::io::Seek;
use binrw::{binrw, BinRead, BinReaderExt, NullString};
pub mod incoming {
use super::*;
$(#[$attrs])*
pub struct $struct_name {
$(
$(#[$field_attr])*
pub $field_name: $field_type
),*
}
#[derive(Debug, Default)]
pub struct Metadata {
$(
pub $field_name: (usize, usize),
)*
}
impl $struct_name {
pub fn read_packet(data: &[u8]) -> anyhow::Result<(Self, Metadata)> {
let mut reader = std::io::Cursor::new(data);
let mut metadata = Metadata::default();
let instance = Self::read(&mut reader)?;
println!("R: {:?}", reader.reads);
// this not work because macros cannot read fields with #[br] attr
// $(
// let start = reader.position();
// let $field_name = <$field_type as BinRead>::read_le(&mut reader)?;
// let end = reader.position();
// metadata.$field_name = (start, end - start);
// )*
Ok((instance, metadata))
}
}
}
}
}
}
to calculate the offsets and sizes I try to use position()
method, but when some of the fields of my struct contains #[br]
attr this code will fail:
$(
let start = reader.position();
let $field_name = <$field_type as BinRead>::read_le(&mut reader)?;
let end = reader.position();
metadata.$field_name = (start, end - start);
)*
with error:
error[E0277]: the trait bound `VecArgs<()>: Required` is not satisfied
--> src/main.rs:125:15
|
125 | text: Vec<u8>,
| ^^^^^^^ the trait `Default` is not implemented for `VecArgs<()>`
so seems like I should use the method like read_options
or smth like that, but the question then: how can I parse the options from the token properly.
I also tried to implement the wrapper for the Cursor (in case I can store needed data on each read):
pub struct TrackedCursor<T> {
inner: std::io::Cursor<T>,
reads: Vec<(usize, usize)>, // (offset, length)
}
impl<T> TrackedCursor<T> {
pub fn new(inner: T) -> Self
where
T: AsRef<[u8]>,
{
Self {
inner: std::io::Cursor::new(inner),
reads: Vec::new(),
}
}
pub fn reads(&self) -> &[(usize, usize)] {
&self.reads
}
pub fn into_inner(self) -> (T, Vec<(usize, usize)>) {
(self.inner.into_inner(), self.reads)
}
}
impl<T> Read for TrackedCursor<T>
where
T: AsRef<[u8]>,
{
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
let offset = self.inner.position() as usize;
let bytes_read = self.inner.read(buf)?;
if bytes_read > 0 {
self.reads.push((offset, bytes_read));
}
Ok(bytes_read)
}
}
impl<T> Seek for TrackedCursor<T>
where
T: AsRef<[u8]>,
{
fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
self.inner.seek(pos)
}
}
but this not work for me since it read smart-pointers/arrays and so on in different way:
- NullString it reads byte-per-byte until zero
- [u16; 4] it reads 4 times
- etc
so the output of the reads
field of the Wrapper for the struct below:
make_packet! {
#[binrw]
#[brw(little)]
#[derive(Debug)]
struct Message {
len: u16,
len2: [u16; 2],
len3: NullString,
#[br(count = len)]
text: Vec<u8>,
}
}
will be this:
[(0, 2), (2, 2), (4, 2), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1), (12, 1), (13, 1), (14, 1), (15, 1), (16, 1)]
which is actually not what I expect.
So my question is: what is the proper way to read and store metadata for each field ? For the struct from example I expect 4 metadata items which I prefer to be stored in the Metadata
struct - if this even possible.
Could somebody advice me what can I do ?
P.S. not sure if parse_with
attr can help me since each field in the struct can contains multiple attrs and I would like to avoid implementing custom parsers for every struct I have.