1

Why don't I get an error from bincode when I try to deserialize binary data into the wrong type?

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);
}

I was expecting to get a Message::Ping but I get:

Heartbeat (
    Heartbeat {
        term: 4294967296,
        node_id: 8589934592,
    },
)
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Maksim
  • 41
  • 1
  • 7
  • Said another way, is `0xFFFF` two `u8` values or one `u16`? How do you know? *How would bincode*? – Shepmaster Apr 27 '21 at 19:56
  • @Stargateur Sure but it seems unnecessarily verbose to write `Message::V(VoteRequest...` everywhere when `VoteRequest` is already a serializable type. – Maksim Apr 27 '21 at 20:20
  • @Stargateur that's not what I expect. As I said: I expected an `Err` and in the serverless MRE my expectations were satisfied. Why don't I get such an `Err` when bytes are sent over a socket? – Maksim Apr 27 '21 at 20:35
  • Don't hesitate to rollback your question if you think we didn't understand what you wanted to ask – Stargateur Apr 27 '21 at 20:57
  • @Stargateur I'm sorry I've stepped away from my desk; does this amended code generate an `Err`? Or does it simply deserialize to the wrong variant? I'll just point out that in my original post my question was bolded ie why was there no `Err` when bincode deserialized. – Maksim Apr 27 '21 at 21:11

1 Answers1

1

bincode trust the user to deserialize into the expected type, what you are doing have "random" result, it's safe but it's implementation behavior.

The following is simply an example, this COULD be wrong, but the logic is correct. enum in rust are implementation behavior, bincode is "abusing" rust by assuming an enum is always represented with a unsigned integer value, bincode also choice to encode it as u32 value "enums variants are encoded as a u32 instead of a usize. u32 is enough for all practical uses.". This is not important from a user point of view (except the "limitation" of enum with max 2**32 variant...).

So, this is how bincode do. In your code, you are asking bincode to encore a Ping structure, NOT the variant Message::Ping.

This mean the encode buffer will contain 3 usize like Ping structure. Then you ask bincode to interpret this data as a Message enum, basically this will ask bincode to read from the buffer a u32, in this example this would result by reading 0, and this happen to be the number rust and bincode used to represent the first variant of Message enum. So bincode will think "ok I'm reading a Message::Heartbeat then bincode will read 2 more usize to fill up the Heartbeat structure. Like reading a u32 in a 64 bit system will introduce an offset of 4 octets, bincode will not read 1 and 2 but 1 << 32 and 2 << 32.

This mean that in the encoded buffer you have something like that

[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       $

From the point of view of bincode this is perfectly valid. bincode is mean to be using with reader, and so the reader cursor would still have 4 octets left to read.

We can play a bit, If you change a little bit the encoded value pinger_id: usize::MAX, you would have an error message:

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

We could also play by changing the first usize from Ping to u32 doing:

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

and now encoding with these values:

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

would result into having 1 and 2:

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

if Ping structure is too small:

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

bincode would error saying there is missing data:

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

So, in summary you must not send the "direct" value of an variant if you deserialize it into an enum type. When using bincode or any serializing tool, you must always match the type you encoded with the type you decode and so you MUST serialize an Message::Ping(Ping{ .. }) not a Ping { .. } directly.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • Thank you for writing this out. It is indeed what I realized after you pointed it out and after thinking about it some. The one question that remains is why for my 4 line MRE I did get an `Err` whereas when the socket was the intermediary I did not. I'm guessing it has to do with the type system being more helpful when the buffer is filled "locally". – Maksim Apr 27 '21 at 21:56
  • @Maksim update with another example that is the same behavior than your MRE basically your real use case you probably send many ping or ping and some other thing that would allow to have more octet receive and so bincode would have been feed with enough data inside the buffer. – Stargateur Apr 27 '21 at 22:00
  • @ Stargateur it was already there. Here it is again: `let p = Ping{ pinger_id: 0 }; let ser = bincode::serialize(&p).unwrap(); let m: Message = bincode::deserialize(&ser).unwrap(); println!("{:?}", m);`. This gives me `Io(Kind(UnexpectedEof))`. – Maksim Apr 27 '21 at 22:51
  • @Maksim you are confuse that exactly what I added after you post your comment and that exactly what happen in your MRE and I explain in my answer why it's happening please look edit history if it's not clear enough. – Stargateur Apr 28 '21 at 00:17