3 个不稳定版本
0.2.0 | 2022年11月23日 |
---|---|
0.1.1 | 2022年11月22日 |
0.1.0 | 2022年11月21日 |
#2487 在 解析器实现
130KB
2.5K SLoC
Marked Binary Object Notation
mbon 是一种受 NBT 格式启发的二进制表示。
它由一系列强类型值组成。每个由两部分组成:一个标记,它定义了数据类型和大小,然后是数据。标记可以有不同的尺寸,因此使用单个字节前缀来区分类型。
此格式是自描述的,这意味着它可以知道数据是否格式不正确或存储了与预期不同的类型。该格式的自描述特性的另一个特点是,您可以在不需要解析整个项目的情况下跳过数据中的值,例如,可以通过只读取标记来轻松跳过 1GB 的值。
用法
转储
您可以使用 dumper::Dumper 结构来转储二进制数据。您可以直接写入值或使用 serde 的序列化来写入更复杂的数据。
use mbon::dumper::Dumper;
let a = 32;
let b = "Hello World";
let c = b'a';
let mut dumper = Dumper::new();
dumper.write_int(a).unwrap();
dumper.write(&b).unwrap();
dumper.write(&c).unwrap();
let output = dumper.writer();
assert_eq!(output, b"i\x00\x00\x00\x20s\x00\x00\x00\x0bHello Worldca");
解析
您可以使用 parser::Parser 结构来解析二进制数据。您可以直接解析值,但建议使用 serde 来解析数据。
use mbon::parser::Parser;
use mbon::data::Value;
let data = b"i\x00\x00\x00\x20s\x00\x00\x00\x0bHello Worldca";
let mut parser = Parser::from(data);
let a = parser.next_value().unwrap();
let b: String = parser.next().unwrap();
let c: u8 = parser.next().unwrap();
if let Value::Int(a) = a {
assert_eq!(a, 32);
} else {
panic!("a should have been an int");
}
assert_eq!(b, "Hello World");
assert_eq!(c, b'a');
嵌入式对象
如果您想在格式中嵌入预定义的对象,您可以实现 object::ObjectDump/object::ObjectParse。请注意,您需要调用 write_obj()
/parse_obj()
来利用它。
use mbon::parser::Parser;
use mbon::dumper::Dumper;
use mbon::error::Error;
use mbon::object::{ObjectDump, ObjectParse};
#[derive(Debug, PartialEq, Eq)]
struct Foo {
a: i32,
b: String,
c: char,
}
impl ObjectDump for Foo {
type Error = Error;
fn dump_object(&self) -> Result<Vec<u8>, Self::Error> {
let mut dumper = Dumper::new();
dumper.write(&self.a)?;
dumper.write(&self.b)?;
dumper.write(&self.c)?;
Ok(dumper.writer())
}
}
impl ObjectParse for Foo {
type Error = Error;
fn parse_object(object: &[u8]) -> Result<Self, Self::Error> {
let mut parser = Parser::new(object);
let a = parser.next()?;
let b = parser.next()?;
let c = parser.next()?;
Ok(Self { a, b, c })
}
}
let foo = Foo { a: 32, b: "Hello World".to_owned(), c: '🫠' };
let mut dumper = Dumper::new();
dumper.write_obj(&foo).unwrap();
let buf = dumper.writer();
let mut parser = Parser::from(&buf);
let new_foo: Foo = parser.next_obj().unwrap();
assert_eq!(foo, new_foo);
异步实现
如果您想异步解析数据,您可能想使用提供的包装器: async_wrapper::AsyncDumper, async_wrapper::AsyncParser。
use futures::io::{AsyncWriteExt, Cursor};
use mbon::async_wrapper::{AsyncDumper, AsyncParser};
let writer = Cursor::new(vec![0u8; 5]);
let mut dumper = AsyncDumper::from(writer);
dumper.write(&15u32)?;
dumper.flush().await?;
let mut reader = dumper.writer();
reader.set_position(0);
let mut parser = AsyncParser::from(reader);
let val: u32 = parser.next().await?;
assert_eq!(val, 15);
语法
以下为二进制格式的语法。请注意,所有数字都按大端形式存储。
Value ::= long | int | short | char | float | double | null | bytes
| str | object | enum | array | list | dict | map;
Mark ::= Mlong | Mint | Mshort | Mchar | Mfloat | Mdouble | Mnull
| Mbytes | Mstr | Mobject | Menum | Marray | Mlist | Mdict | Mmap;
Data ::= Dlong | Dint | Dshort | Dchar | Dfloat | Ddouble | Dnull
| Dbytes | Dstr | Dobject | Denum | Darray | Dlist | Ddict | Dmap;
u32 ::= <u8> <u8> <u8> <u8>;
i64 ::= <u8> <u8> <u8> <u8> <u8> <u8> <u8> <u8>;
i32 ::= <u8> <u8> <u8> <u8>;
i16 ::= <u8> <u8>;
i8 ::= <u8>;
f64 ::= <u8> <u8> <u8> <u8> <u8> <u8> <u8> <u8>;
f32 ::= <u8> <u8> <u8> <u8>;
Mlong ::= 'l';
Mint ::= 'i';
Mshort ::= 'h';
Mchar ::= 'c';
Mfloat ::= 'f';
Mdouble ::= 'd';
Mnull ::= 'n';
Mbytes ::= 'b' u32;
Mstr ::= 's' u32;
Mobject ::= 'o' u32;
Menum ::= 'e' Mark#enum;
Marray ::= 'a' Mark#item u32;
Mlist ::= 'A' u32;
Mdict ::= 'm' Mark#key Mark#value u32;
Mmap ::= 'M' u32;
Dlong ::= i64;
Dint ::= i32;
Dshort ::= i16;
Dchar ::= i8;
Dfloat ::= f32;
Ddouble ::= f64;
Dnull ::= ;
Dbytes ::= u8 Dbytes |;
Dstr ::= u8 Dbytes |;
Dobject ::= u8 Dbytes |;
Denum ::= u32 Data#enum;
Darray ::= Data#item Darray |;
Dlist ::= Value Dlist |;
Ddict ::= Data#key Data#value Ddict |;
Dmap ::= Value Value Dmap |;
long ::= Mlong Dlong;
int ::= Mint Dint;
short ::= Mshort Dshort;
char ::= Mchar Dchar;
float ::= Mfloat Dflaot;
double ::= Mdouble Ddouble;
null ::= Mnull;
bytes ::= Mbytes Dbytes;
str ::= Mstr Dstr;
object ::= Mobject Dbytes;
enum ::= Menum Denum;
array ::= Marray Darray;
list ::= Mlist Dlist;
dict ::= Mdict Ddict;
map ::= Mmap Dmap;
X#name
表示它与任何其他Y#name
相关,例如Mark#item
在Marray
中与Data#item
相关。
规范
名称 | 描述 |
---|---|
长度 | 64位整数 |
整型 | 32位整数 |
短整型 | 16位整数 |
字符型 | 8位整数 |
浮点型 | 32位IEEE-754浮点数 |
双精度浮点型 | 64位IEEE-754浮点数 |
空值 | 仅标记 |
字节 | 未编码的字节字符串 |
字符串 | UTF-8编码的字符串 |
对象 | 嵌入的预格式化数据 |
枚举 | u32变体,嵌入数据 |
数组 | len 列表中的 item 数据 |
列表 | 值的列表 |
字典 | len 列表中的 key -value 对 |
映射 | 键值对列表 |
数字
每个数字只通过它们的标记来定义。数字的标记中不存储其他数据。
所有数字都以大端二进制形式存储。整数在内部被认为是带符号的,但没有要求它们必须是带符号的,因此可以按带符号整数读取无符号整数。
字符串
字符串将存储它们的类型标记,后跟一个表示其长度的 u32,例如 b"s\x00\x00\x00\x05"
表示一个长度为5个字节的字符串。字符串必须使用UTF-8编码;如果您不希望这种行为,可以使用字节,它与 Str 的行为相同,但没有UTF-8要求。
对象
如果您想嵌入二进制数据,可以使用对象值。这与字节值类似,但它使用一个无符号整数来表示长度。它用于存储具有预定格式的二进制数据。
枚举
枚举旨在与Rust的枚举序列化兼容。它由一个变体ID后跟一个嵌入的值定义。为了使枚举自描述,嵌入值的标记放置在枚举的标记内。
空值
空值仅使用其标记,没有与之关联的数据。
数组
序列可以以两种形式存储;数组比列表更为严格。如果序列不能作为数组存储,它将作为列表存储。
数组是具有相同标记的项目序列。这意味着所有元素必须具有相同的大小:u32的向量始终作为数组存储,而字符串的向量只能在其每个字符串长度相同的情况下作为数组存储。
数组标记包含包含项目的标记,后跟一个表示项目数量的 u16。
列表
列表是存储序列的更宽松方式。它简单地保留所有项目的序列。列表的标记简单地保存列表中的字节数,作为一个 u32。
字典
字典类似于数组,但它存储键值对。所有键必须共享相同的标记,所有值也必须共享相同的标记。
字典标记包含键标记,后面是值标记,最后是字典中的项目数量。
映射
映射类似于列表,但以键值格式存储任何值类型。这是如果值不能作为字典存储时的回退格式。
依赖关系
~200-600KB
~12K SLoC