bibeam_protocol/codec.rs
1#![forbid(unsafe_code)]
2//! Postcard codec for [`Frame`].
3//!
4//! On the wire every `BiBeam` message is laid out as:
5//!
6//! ```text
7//! MAGIC (4 bytes) || VERSION (1 byte) || postcard-serialised Frame
8//! ```
9//!
10//! [`encode`] produces that exact byte layout. [`decode`] validates both
11//! [`MAGIC`] and [`VERSION`] before invoking the postcard deserializer
12//! and surfaces the two bad-prefix cases as first-class
13//! [`ProtocolError::BadMagic`] and [`ProtocolError::BadVersion`]
14//! variants, so callers do not need to string-sniff a generic codec
15//! error to find the root cause.
16
17use bytes::Bytes;
18use postcard::Error as PostcardError;
19
20use crate::error::ProtocolError;
21use crate::frame::{Frame, MAGIC, VERSION};
22
23/// Size of the fixed envelope prefix written ahead of every postcard
24/// payload: four magic bytes followed by one version byte.
25const PREFIX_LEN: usize = MAGIC.len() + 1;
26
27/// Encode `frame` into the canonical `BiBeam` wire layout.
28///
29/// The output is `MAGIC || VERSION || postcard(frame)`. The returned
30/// [`Bytes`] is freshly allocated; callers may share it across tasks
31/// cheaply because [`Bytes`] is reference-counted.
32pub fn encode(frame: &Frame) -> Result<Bytes, PostcardError> {
33 let payload = postcard::to_stdvec(frame)?;
34 let mut buf = Vec::with_capacity(PREFIX_LEN + payload.len());
35 buf.extend_from_slice(&MAGIC);
36 buf.push(VERSION);
37 buf.extend_from_slice(&payload);
38 Ok(Bytes::from(buf))
39}
40
41/// Decode a wire buffer into a [`Frame`].
42///
43/// Returns [`ProtocolError::BadMagic`] if the first four bytes are not
44/// [`MAGIC`], [`ProtocolError::BadVersion`] if the version byte does
45/// not equal [`VERSION`], and [`ProtocolError::Codec`] for any
46/// underlying postcard failure (including a buffer shorter than the
47/// prefix, which postcard surfaces as `DeserializeUnexpectedEnd`).
48pub fn decode(buf: &[u8]) -> Result<Frame, ProtocolError> {
49 if buf.len() < PREFIX_LEN {
50 return Err(ProtocolError::Codec(PostcardError::DeserializeUnexpectedEnd));
51 }
52 if buf[..MAGIC.len()] != MAGIC {
53 return Err(ProtocolError::BadMagic);
54 }
55 let version = buf[MAGIC.len()];
56 if version != VERSION {
57 return Err(ProtocolError::BadVersion {
58 got: version,
59 expected: VERSION,
60 });
61 }
62 let frame = postcard::from_bytes(&buf[PREFIX_LEN..])?;
63 Ok(frame)
64}