1

当我尝试将二进制数据反序列化为错误类型时,为什么我不会从 bincode 中收到错误消息?

use bincode; // 1.3.1
use serde::{Deserialize, Serialize}; // { version = "1.0", features = ["derive"] }

#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Ping {
    pub pinger_id: usize,
    pub useless_field: usize,
    pub i_need_one_more: usize,
}

#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Heartbeat {
    pub term: usize,
    pub node_id: usize,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Message {
    Heartbeat(Heartbeat),
    Ping(Ping),
}

fn main() {
    let rpc_message_bin = bincode::serialize(&Ping {
        pinger_id: 0,
        useless_field: 1,
        i_need_one_more: 2,
    })
    .unwrap();
    let m: Message = bincode::deserialize(&rpc_message_bin).unwrap();

    println!("{:#?}", m);
}

我期待得到一个Message::Ping,但我得到:

Heartbeat (
    Heartbeat {
        term: 4294967296,
        node_id: 8589934592,
    },
)
4

1 回答 1

1

bincode 信任用户反序列化为预期的类型,你正在做的事情有“随机”的结果,它是安全的,但它是实现行为。

下面只是一个例子,这可能是错误的,但逻辑是正确的。enum在 rust 中是实现行为,bincode 通过假设 anenum始终用无符号整数值表示来“滥用”rust,bincode 还选择将其编码为u32“枚举变体被编码为 au32而不是 a usize.u32足以满足所有实际用途。 " . 从用户的角度来看,这并不重要(除了enummax2**32变体的“限制”......)。

所以,这就是 bincode 的做法。在您的代码中,您要求 bincode 重新Ping构建结构,而不是变体Message::Ping

这意味着编码缓冲区将包含3 usize类似的Ping结构。然后你让 bincode 将此数据解释为 a Message enum,基本上这将要求 bincode 从缓冲区 a 中读取u32,在本例中这将通过读取产生0,而这恰好是数字 rust 和 bincode 用于表示 的第一个变体Message enum。所以 bincode 会认为“好的,我正在读取一个Message::Heartbeat然后 bincode 将读取 2 个更多的 usize 来填充Heartbeat结构。就像在 64 位系统中读取一个 u32 会引入4八位字节的偏移量,bincode 不会读取1and2而是1 << 32and 2 << 32

这意味着在编码缓冲区中你有类似的东西

[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]
 ^ first usize        $  ^ second usize       $  ^ last usize         $
 ^ u32    $  ^ first usize       $  ^ second usize       $

从 bincode 的角度来看,这是完全有效的。bincode 意味着要与阅读器一起使用,因此阅读器光标仍有4八位字节可供阅读。

我们可以玩一下,如果你稍微改变一下编码的值pinger_id: usize::MAX,你会得到一个错误信息:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom("invalid value: integer `4294967295`, expected variant index 0 <= i < 2")', src\main.rs:31:61

我们也可以通过将第一个usizefrom更改Pingu32做来玩:

#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Ping {
    pub pinger_id: u32,
    pub useless_field: usize,
    pub i_need_one_more: usize,
}

现在使用这些值进行编码:

    let rpc_message_bin = bincode::serialize(&Ping {
        pinger_id: 0,
        useless_field: 1,
        i_need_one_more: 2,
    })

将导致拥有1and 2

Heartbeat(
    Heartbeat {
        term: 1,
        node_id: 2,
    },
)

如果Ping结构太小:

#[derive(Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Ping {
    pub pinger_id: usize,
}

bincode 会出错,说缺少数据:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Io(Kind(UnexpectedEof))', src\main.rs:27:61

因此,总而言之,如果将变体反序列化为枚举类型,则不得发送变体的“直接”值。使用 bincode 或任何序列化工具时,您必须始终将您编码的类型与您解码的类型匹配,因此您必须直接序列化 aMessage::Ping(Ping{ .. })而不是 a Ping { .. }

于 2021-04-27T21:37:12.603 回答