#binary-encoding #binary #serialization #deserialize #byte-length

desert

为 {de,}序列化紧凑的二进制格式提供特性和函数

6 个稳定版本

2.0.1 2022年3月11日
2.0.0 2021年5月13日
1.0.3 2020年3月27日
1.0.1 2020年3月16日
1.0.0 2020年3月15日

#964 in 编码


6 crates 中使用

MIT/Apache

40KB
857

desert

为 {de,}序列化紧凑的二进制格式提供特性和函数

  • 提供内置容器类型(元组、数组、切片、向量)的紧凑表示。
  • 切片和向量的 varint 长度编码方案(长度小于 128 时使用 1 个字节)
  • 强调自定义二进制 {de,}序列化器的实现 ergonomics

此 crate 目前不提供自动推导。相反,重点在于提供一套良好的内置容器的紧凑实现,并使手动实现自己的二进制格式变得简单。

示例

您可以使用内置类型和容器的方方法

use desert::{ToBytes,ToBytesBE,ToBytesLE,FromBytesBE,CountBytes};
type Error = Box<dyn std::error::Error+Send+Sync>;

fn main() -> Result<(),Error> {
  // no overhead for tuples and arrays
  assert_eq![(5u16,6u16,7u16).to_bytes_be()?, vec![0,5,0,6,0,7]];
  assert_eq![[5u16,6u16,7u16].to_bytes_be()?, vec![0,5,0,6,0,7]];

  // minimal overhead for slices and vectors using varints
  assert_eq![
    vec![100u16,101u16,102u16].as_slice().to_bytes_be()?,
    vec![6,0,100,0,101,0,102]
  ];
  assert_eq![vec![0u8;500].count_bytes(), 502];

  // without endianness defaults to big-endian
  assert_eq![(5u16,6u16).to_bytes()?, vec![0,5,0,6]];
  assert_eq![(5u16,6u16).to_bytes_be()?, vec![0,5,0,6]];
  assert_eq![(5u16,6u16).to_bytes_le()?, vec![5,0,6,0]];

  // construct an array from bytes and get the size of bytes read
  assert_eq![
    <[u16;2]>::from_bytes_be(&vec![0,5,0,6])?,
    (4,[5u16,6u16])
  ];
  // this way you can load data structures from slices with extra at the end
  assert_eq![
    <[u16;2]>::from_bytes_be(&vec![0,5,0,6,7,8,9,10,11])?,
    (4,[5u16,6u16])
  ];

  // count how many bytes will need to be read for this Vec<u16>
  assert_eq![
    <Vec<u16>>::count_from_bytes(&vec![6,0,100,0,101,0,102,55,44,33,22,11])?,
    7
  ];

  Ok(())
}

您还可以定义自己的类型

use desert::{ToBytes,FromBytes};
type Error = Box<dyn std::error::Error+Send+Sync>;

#[derive(Debug)]
enum Item { A((f32,f32)), B(u32) }

#[derive(Debug)]
struct Custom { foo: u64, items: Vec<Item> }

impl ToBytes for Custom {
  fn to_bytes(&self) -> Result<Vec<u8>,Error> {
    let mut bytes = vec![];
    // Store foo (in big endian).
    bytes.extend(&self.foo.to_bytes()?);

    // Store the number of items (in big endian).
    bytes.extend(&(self.items.len() as u16).to_bytes()?);

    // Use a bitfield to more compactly represent
    // whether an Item is an A or B.
    let mut bitfield = vec![0u8;(self.items.len()+7)/8];
    for (i,item) in self.items.iter().enumerate() {
      bitfield[i/8] |= match item {
        Item::A(_) => 0,
        Item::B(_) => 1,
      } << (i%8);
    }
    bytes.extend(bitfield);

    // Write out each item serially.
    for item in self.items.iter() {
      bytes.extend(match item {
        Item::A(x) => x.to_bytes()?,
        Item::B(x) => x.to_bytes()?
      });
    }
    Ok(bytes)
  }
}

impl FromBytes for Custom {
  fn from_bytes(src: &[u8]) -> Result<(usize,Self),Error> {
    let mut offset = 0;

    // Read foo (in big endian).
    let (size,foo) = u64::from_bytes(&src[offset..])?;
    offset += size;

    // Read the number of items (in big endian).
    let (size,item_len) = u16::from_bytes(&src[offset..])?;
    offset += size;

    // Read the bitfield data but keep it as a u8 slice.
    let bitfield_len = ((item_len+7)/8) as usize;
    let bitfield = &src[offset..offset+bitfield_len];
    offset += bitfield_len;

    // Read the items, checking the bitfield to know whether an item
    // is an A or a B.
    let mut items = vec![];
    for i in 0..item_len as usize {
      if (bitfield[i/8]>>(i%8))&1 == 0 {
        let (size,x) = <(f32,f32)>::from_bytes(&src[offset..])?;
        items.push(Item::A(x));
        offset += size;
      } else {
        let (size,x) = u32::from_bytes(&src[offset..])?;
        items.push(Item::B(x));
        offset += size;
      }
    }
    Ok((offset, Custom { foo, items }))
  }
}

fn main() -> Result<(),Error> {
  let bytes = Custom {
    foo: 1234567890123456789,
    items: vec![
      Item::A((3.0,4.2)),
      Item::B(1337),
      Item::A((5.5,6.6))
    ]
  }.to_bytes()?;
  println!["serialized: {:?}", bytes];

  let (size,custom) = Custom::from_bytes(&bytes)?;
  println!["deserialized {} bytes: {:?}", size, custom];
  Ok(())
}

原因

Rust 内核在内置类型上定义了一些有用的方法:5000u32.to_be_bytes()u32::from_be_bytes([u8;4]) 等。

这些方法当然很有用,但它们属于内置类型,而不是特性和函数。这使得编写一个接受任何可以序列化为字节的泛型接口变得困难。

其他选项,如使用 bincode 的 serde,允许您推导出自定义的 Serialize 和 Derive 实现,这非常方便,您还可以支持许多其他输出格式,而不仅仅是二进制格式。但是,如果您想实现自己的自定义序列化和解序列化,事情会变得非常困难。对于解序列化,您需要实现一个 Visitor,事情很快就会变得混乱。

bincode 也做出了权衡,这些权衡对于快速将数据打包进内存和从内存中解包,同时最小化解析开销是有意义的。但是,这个选择意味着向量开头会有 usize 字节填充,在 64 位机器上为 8 个完整的字节,而自动推导的枚举将始终以 u32 开头,即使只有 2 个选项可以枚举也是如此。这些权衡对于您需要更多精细控制结构在字节级别表示的情况,或者为了降低开销或与现有的外部定义的线协议集成的情况来说并不理想。但是,为了使用 serde 和 bincode 实现那些自定义的字节格式,您可能需要实现非常通用且困难的 serde 接口,而您可能只关心网络或磁盘上的字节。

在处理二进制数据时,另一个常见问题是从网络或磁盘读取由网络传输或物理介质决定大小的大量数据块。这些大小很可能无法很好地映射到您的数据结构,因此能够计数(而不解析到新实例)可以从 [u8] 切片中读取多少字节,以便达到考虑中的特定数据结构的结束字节偏移量,这可能具有动态大小。或者,了解当数据结构的表示超出缓冲区切片的末尾,程序应从网络或磁盘上获取更多数据时,也可能很有帮助。这些关注点由 CountBytes 特性提供。

api

阅读完整文档

该软件包包含用于处理二进制数据的这些特性

type Error = Box<dyn std::error::Error+Send+Sync>;

pub trait ToBytes {
  fn to_bytes(&self) -> Result<Vec<u8>,Error>;
  fn write_bytes(&self, dst: &mut [u8]) -> Result<usize,Error>;
}
pub trait ToBytesBE {
  fn to_bytes_be(&self) -> Result<Vec<u8>,Error>;
  fn write_bytes_be(&self, dst: &mut [u8]) -> Result<usize,Error>;
}
pub trait ToBytesLE {
  fn to_bytes_le(&self) -> Result<Vec<u8>,Error>;
  fn write_bytes_le(&self, dst: &mut [u8]) -> Result<usize,Error>;
}

pub trait FromBytes: Sized {
  fn from_bytes(src: &[u8]) -> Result<(usize,Self),Error>;
}
pub trait FromBytesBE: Sized {
  fn from_bytes_be(src: &[u8]) -> Result<(usize,Self),Error>;
}
pub trait FromBytesLE: Sized {
  fn from_bytes_le(src: &[u8]) -> Result<(usize,Self),Error>;
}

pub trait CountBytes {
  fn count_from_bytes(buf: &[u8]) -> Result<usize,Error>;
  fn count_from_bytes_more(buf: &[u8]) -> Result<Option<usize>,Error>;
  fn count_bytes(&self) -> usize;
}
pub trait CountBytesBE {
  fn count_from_bytes_be(buf: &[u8]) -> Result<usize,Error>;
  fn count_from_bytes_be_more(buf: &[u8]) -> Result<Option<usize>,Error>;
  fn count_bytes_be(&self) -> usize;
}
pub trait CountBytesLE {
  fn count_from_bytes_le(buf: &[u8]) -> Result<usize,Error>;
  fn count_from_bytes_le_more(buf: &[u8]) -> Result<Option<usize>,Error>;
  fn count_bytes_le(&self) -> usize;
}

以及以下实现

  • u8, u16, u32, u64, u128
  • i8, i16, i32, i64, i128
  • f32, f64, bool
  • 元组(最多12个元素)
  • 数组、向量和切片

许可证

MIT 或 Apache-2.0

无运行时依赖