【问题标题】:Deserialize Vec<u8> with nonstandard length encoding使用非标准长度编码反序列化 Vec<u8>
【发布时间】:2021-09-13 12:35:12
【问题描述】:

我正在尝试将 serde 与 bincode 一起使用来反序列化任意 bitcoin network message。鉴于有效负载普遍作为字节数组处理,如果在编译时长度未知,我该如何反序列化它? bincode 默认处理Vec&lt;u8&gt;,假设它的长度在向量元素之前被编码为u64。但是,此假设在这里不成立,因为 校验和 位于有效负载的 length 之后。

我有以下工作解决方案

Cargo.toml

[package]
name = "serde-test"
version = "0.1.0"
edition = "2018"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_bytes = "0.11"
bincode = "1.3.3"

ma​​in.rs

use bincode::Options;
use serde::{Deserialize, Deserializer, de::{SeqAccess, Visitor}};

#[derive(Debug)]
struct Message {
    // https://en.bitcoin.it/wiki/Protocol_documentation#Message_structure
    magic: u32,
    command: [u8; 12],
    length: u32,
    checksum: u32,
    payload: Vec<u8>,
}

struct MessageVisitor;
impl<'de> Visitor<'de> for MessageVisitor {
        type Value = Message;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("Message")
    }

    fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error> where V: SeqAccess<'de>,
    {
        let magic = seq.next_element()?.unwrap();
        let command = seq.next_element()?.unwrap();
        let length: u32 = seq.next_element()?.unwrap();
        let checksum = seq.next_element()?.unwrap();
        let payload = (0..length).map(|_| seq.next_element::<u8>().unwrap().unwrap()).collect();
        // verify payload checksum (omitted for brevity)

        Ok(Message {magic, command, length, checksum, payload})
    }
}

impl<'de> Deserialize<'de> for Message {
    fn deserialize<D>(deserializer: D) -> Result<Message, D::Error> where D: Deserializer<'de>,
    {
        deserializer.deserialize_tuple(5000, MessageVisitor) // <-- overallocation
    }
}

fn main() {
    let bytes = b"\xf9\xbe\xb4\xd9version\x00\x00\x00\x00\x00e\x00\x00\x00_\x1ai\xd2r\x11\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\xbc\x8f^T\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc6\x1bd\t \x8d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\x00q\xc0 \x8d\x12\x805\xcb\xc9yS\xf8\x0f/Satoshi:0.9.3/\xcf\x05\x05\x00\x01";

    let msg: Message = bincode::DefaultOptions::new().with_fixint_encoding().deserialize(bytes).unwrap();

    println!("{:?}", msg);
}

输出:

Message { magic: 3652501241, command: [118, 101, 114, 115, 105, 111, 110, 0, 0, 0, 0, 0], length: 101, checksum: 3530103391, payload: [114, 17, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 188, 143, 94, 84, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 198, 27, 100, 9, 32, 141, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 203, 0, 113, 192, 32, 141, 18, 128, 53, 203, 201, 121, 83, 248, 15, 47, 83, 97, 116, 111, 115, 104, 105, 58, 48, 46, 57, 46, 51, 47, 207, 5, 5, 0, 1] }

我不喜欢这个解决方案,因为 payload 的处理方式。它需要我分配一些“足够大”的缓冲区来考虑payload 的动态大小,在代码sn-p 中5000 以上就足够了。我宁愿将payload 反序列化为单个元素,而改用deserializer.deserialize_tuple(5, MessageVisitor)


有没有办法以简洁的方式处理这种反序列化?

我可以找到类似的问题:Can I deserialize vectors with variable length prefix with Bincode?

【问题讨论】:

  • 也许尝试使用nom crate?或者尝试逐字节解析并分组为数字,例如let length = u32::from_le_bytes([...])?
  • @Coder-256 直到现在我还没有听说过nom crate,它是serde 的替代品吗?我不确定逐字节解析是什么意思,length 编码并不是真正的问题,而是它没有立即跟随payload 中的元素。
  • 你不能使用 bincode 并称之为一天 LUL bincode 仅用于 bincode 编码。使用名词
  • @Kevin nom 不是 serde 的替代品,它只是一种不同的方法。您的问题是您的消息未编码为 bincode,因此您正在做一些奇怪的事情来处理非 bincode 数据。 Serde 用于为通用格式创建序列化器和反序列化器,但您的消息采用非常特定的格式,只能以一种方式解释。像 nom 这样的库更合适。您也可以直接查看字节,因为它是一种简单的格式。

标签: rust serde bincode


【解决方案1】:

您的问题是源消息未编码为 bincode,因此您正在做一些奇怪的事情来处理非 bincode 数据。

Serde 旨在为通用格式创建序列化程序和反序列化程序,但您的消息采用非常特定的格式,只能以一种方式解释。

像 nom 这样的库更适合这种工作,但考虑到格式有多简单并且您可以直接从字节中解析它,它可能有点矫枉过正:

use std::convert::TryInto;

fn main() {
    let bytes = b"\xf9\xbe\xb4\xd9version\x00\x00\x00\x00\x00e\x00\x00\x00_\x1ai\xd2r\x11\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\xbc\x8f^T\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc6\x1bd\t \x8d\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\x00q\xc0 \x8d\x12\x805\xcb\xc9yS\xf8\x0f/Satoshi:0.9.3/\xcf\x05\x05\x00\x01";

    let (magic_bytes, bytes) = bytes.split_at(4);
    let magic = u32::from_le_bytes(magic_bytes.try_into().unwrap());
    let (command_bytes, bytes) = bytes.split_at(12);
    let command = command_bytes.try_into().unwrap();
    let (length_bytes, bytes) = bytes.split_at(4);
    let length = u32::from_le_bytes(length_bytes.try_into().unwrap());
    let (checksum_bytes, bytes) = bytes.split_at(4);
    let checksum = u32::from_le_bytes(checksum_bytes.try_into().unwrap());
    let payload = bytes[..length as usize].to_vec();

    let msg = Message {
        magic,
        command,
        length,
        checksum,
        payload,
    };

    println!("{:?}", msg);
}

Rust 中有数百个加密货币项目,并且已经编写了许多用于处理加密货币数据结构的 crate。这些板条箱经过实战考验,并且具有更好的错误处理能力(我上面的示例没有)。正如cmets中提到的,你或许可以看看bitcoin crate

【讨论】:

    猜你喜欢
    • 2020-10-28
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多