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,
},
)
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.
enumin rust are implementation behavior, bincode is "abusing" rust by assuming anenumis always represented with a unsigned integer value, bincode also choice to encode it asu32value "enums variants are encoded as au32instead of ausize.u32is enough for all practical uses.". This is not important from a user point of view (except the "limitation" ofenumwith max2**32variant...).So, this is how bincode do. In your code, you are asking bincode to encore a
Pingstructure, NOT the variantMessage::Ping.This mean the encode buffer will contain
3usizelikePingstructure. Then you ask bincode to interpret this data as aMessageenum, basically this will ask bincode to read from the buffer au32, in this example this would result by reading0, and this happen to be the number rust and bincode used to represent the first variant ofMessageenum. So bincode will think "ok I'm reading aMessage::Heartbeatthen bincode will read 2 more usize to fill up theHeartbeatstructure. Like reading a u32 in a 64 bit system will introduce an offset of4octets, bincode will not read1and2but1 << 32and2 << 32.This mean that in the encoded buffer you have something like that
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
4octets 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:We could also play by changing the first
usizefromPingtou32doing:and now encoding with these values:
would result into having
1and2:if
Pingstructure is too small:bincode would error saying there is missing data:
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 aPing { .. }directly.