#![cfg(feature = "serialization")]
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::io::{Read, Write};
#[derive(Debug, thiserror::Error)]
pub enum BincodeError {
#[error("bincode encode error: {0}")]
Encode(#[from] bincode::error::EncodeError),
#[error("bincode decode error: {0}")]
Decode(#[from] bincode::error::DecodeError),
#[error("bincode payload contains trailing bytes")]
TrailingBytes,
#[error("bincode I/O error: {0}")]
Io(#[from] std::io::Error),
}
pub fn serialize_into<W: Write, T: Serialize>(
writer: &mut W,
value: &T,
) -> Result<(), BincodeError> {
let config = bincode::config::legacy();
bincode::serde::encode_into_std_write(value, writer, config)?;
Ok(())
}
pub fn deserialize_from<R: Read, T: DeserializeOwned>(reader: &mut R) -> Result<T, BincodeError> {
let config = bincode::config::legacy();
let value = bincode::serde::decode_from_std_read(reader, config)?;
let mut trailing = [0_u8; 1];
if reader.read(&mut trailing)? != 0 {
return Err(BincodeError::TrailingBytes);
}
Ok(value)
}
pub fn serialize<T: Serialize>(value: &T) -> Result<Vec<u8>, BincodeError> {
let config = bincode::config::legacy();
Ok(bincode::serde::encode_to_vec(value, config)?)
}
pub fn deserialize<T: DeserializeOwned>(slice: &[u8]) -> Result<T, BincodeError> {
let config = bincode::config::legacy();
let (value, consumed): (T, usize) = bincode::serde::decode_from_slice(slice, config)?;
if consumed != slice.len() {
return Err(BincodeError::TrailingBytes);
}
Ok(value)
}
#[cfg(test)]
mod wire_format_pins {
use super::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SnapshotHeader {
magic: [u8; 8],
version: u32,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
enum Payload {
Empty,
Bytes(Vec<u8>),
Keyed { id: u64 },
}
#[test]
fn integers_are_fixed_width_little_endian() {
assert_eq!(serialize(&1u64).expect("u64"), [1, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(serialize(&1u32).expect("u32"), [1, 0, 0, 0]);
assert_eq!(serialize(&300u64).expect("u64"), [44, 1, 0, 0, 0, 0, 0, 0]);
assert_eq!(
serialize(&7i64).expect("i64"),
serialize(&7u64).expect("u64"),
"counter_codec depends on u64/i64 byte-identity for non-negative values"
);
assert_eq!(
serialize(&-7i64).expect("i64"),
[249, 255, 255, 255, 255, 255, 255, 255]
);
}
#[test]
fn snapshot_header_is_readable_by_raw_offset() {
let header = SnapshotHeader {
magic: *b"ARTC0001",
version: 2,
};
let bytes = serialize(&header).expect("encode header");
assert_eq!(
&bytes[0..8],
b"ARTC0001",
"magic must be inline, unprefixed"
);
assert_eq!(
u32::from_le_bytes(bytes[8..12].try_into().expect("4 bytes")),
2,
"version must be 4 LE bytes immediately after the magic"
);
assert_eq!(bytes.len(), 12, "no padding or trailing metadata");
}
#[test]
fn sequence_option_and_enum_encodings_are_pinned() {
assert_eq!(
serialize(&vec![1u8, 2, 3]).expect("vec"),
[3, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3]
);
assert_eq!(serialize(&Option::<u8>::None).expect("none"), [0]);
assert_eq!(serialize(&Some(9u8)).expect("some"), [1, 9]);
assert_eq!(
serialize(&Payload::Empty).expect("unit variant"),
[0, 0, 0, 0]
);
assert_eq!(
serialize(&Payload::Keyed { id: 9 }).expect("struct variant"),
[2, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0]
);
assert_eq!(
serialize(&"ab".to_string()).expect("string"),
[2, 0, 0, 0, 0, 0, 0, 0, b'a', b'b']
);
}
#[test]
fn writer_and_slice_paths_agree() {
let value = Payload::Bytes(vec![4, 5, 6]);
let via_slice = serialize(&value).expect("serialize");
let mut via_writer = Vec::new();
serialize_into(&mut via_writer, &value).expect("serialize_into");
assert_eq!(via_writer, via_slice);
assert_eq!(
deserialize::<Payload>(&via_slice).expect("deserialize"),
value
);
assert_eq!(
deserialize_from::<_, Payload>(&mut via_slice.as_slice()).expect("deserialize_from"),
value
);
}
#[test]
fn slice_and_reader_paths_reject_trailing_bytes() {
let mut bytes = serialize(&Payload::Empty).expect("serialize");
bytes.push(0xaa);
assert!(matches!(
deserialize::<Payload>(&bytes),
Err(BincodeError::TrailingBytes)
));
assert!(matches!(
deserialize_from::<_, Payload>(&mut bytes.as_slice()),
Err(BincodeError::TrailingBytes)
));
}
}