1

我正在尝试将 serde 与 bincode 一起使用来反序列化任意比特币网络消息。鉴于有效负载普遍作为字节数组处理,当在编译时长度未知时如何反序列化它?bincode默认情况下Vec<u8>,假设它的长度被编码为u64向量元素之前的处理。然而,这个假设在这里不成立,因为校验和出现在有效载荷的长度之后。

我有以下工作解决方案

货运.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"

main.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,在代码片段中 5000 以上就足够了。我宁愿反序列payload化为单个元素并使用它deserializer.deserialize_tuple(5, MessageVisitor)


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

我可以找到类似的问题:我可以用 Bincode 反序列化具有可变长度前缀的向量吗?

4

1 回答 1

2

您的问题是源消息未编码为 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。这些板条箱经过实战考验,并且具有更好的错误处理能力(我上面的示例没有)。正如评论中提到的,你也许可以看看比特币箱

于 2021-09-13T14:31:47.973 回答