#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum Kind {
Ops = 0,
Hello = 1,
Ping = 2,
Pong = 3,
}
impl Kind {
pub const fn as_byte(self) -> u8 {
self as u8
}
pub const fn from_byte(byte: u8) -> Option<Self> {
match byte {
0 => Some(Kind::Ops),
1 => Some(Kind::Hello),
2 => Some(Kind::Ping),
3 => Some(Kind::Pong),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProtocolVersion(pub u32);
impl ProtocolVersion {
pub const UNKNOWN: ProtocolVersion = ProtocolVersion(0);
pub const fn agrees_with(self, other: ProtocolVersion) -> bool {
self.0 == 0 || other.0 == 0 || self.0 == other.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ping {
pub origin: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Pong {
pub origin: u64,
pub responder: Option<u64>,
}
#[cfg(feature = "serde")]
pub fn answer_ping<C: crate::WireCodec>(codec: &C, ping_body: &[u8], responder: Option<u64>) -> Option<Vec<u8>> {
let ping = codec.decode::<Ping>(ping_body).ok()?;
let mut buf = Vec::with_capacity(PROBE_FRAME_HINT);
begin(Kind::Pong, &mut buf);
codec
.encode_into(
&Pong {
origin: ping.origin,
responder,
},
&mut buf,
)
.ok()?;
Some(buf)
}
#[cfg(feature = "serde")]
pub fn encode_ops<C: crate::WireCodec, Op: serde::Serialize>(
codec: &C,
ops: &[Op],
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let mut out = Vec::new();
begin(Kind::Ops, &mut out);
codec.encode_into(&ops, &mut out)?;
Ok(out)
}
#[cfg(feature = "serde")]
pub fn decode_ops<C: crate::WireCodec, Op: serde::de::DeserializeOwned>(codec: &C, frame: &[u8]) -> Option<Vec<Op>> {
let (tag, body) = split(frame)?;
if Kind::from_byte(tag) != Some(Kind::Ops) {
return None;
}
codec.decode::<Vec<Op>>(body).ok()
}
pub fn split(frame: &[u8]) -> Option<(u8, &[u8])> {
frame.split_first().map(|(kind, body)| (*kind, body))
}
pub const PROBE_FRAME_HINT: usize = 64;
pub fn begin(kind: Kind, buf: &mut Vec<u8>) {
buf.clear();
buf.push(kind.as_byte());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_frame_is_one_tag_byte_and_a_body() {
let mut buf = Vec::new();
begin(Kind::Ops, &mut buf);
buf.extend_from_slice(b"body");
assert_eq!(buf.len(), 5, "one byte of framing, whatever the codec");
let (kind, body) = split(&buf).expect("a non-empty frame splits");
assert_eq!(Kind::from_byte(kind), Some(Kind::Ops));
assert_eq!(body, b"body");
}
#[test]
fn an_unknown_kind_is_skippable_rather_than_fatal() {
assert_eq!(Kind::from_byte(4), None, "the first unassigned byte");
assert_eq!(Kind::from_byte(200), None);
let frame = [200u8, 1, 2, 3];
let (kind, body) = split(&frame).expect("still a well-formed frame");
assert_eq!(Kind::from_byte(kind), None, "unknown, so the frame is skipped");
assert_eq!(body, &[1, 2, 3], "and its body is still delimited");
}
#[test]
fn a_hello_dispatches_to_a_different_body_than_ops() {
assert_eq!(Kind::from_byte(Kind::Hello.as_byte()), Some(Kind::Hello));
assert_ne!(Kind::Ops.as_byte(), Kind::Hello.as_byte());
}
#[test]
fn every_kind_survives_its_own_tag_byte() {
for kind in [Kind::Ops, Kind::Hello, Kind::Ping, Kind::Pong] {
let mut buf = Vec::new();
begin(kind, &mut buf);
buf.extend_from_slice(b"body");
let (tag, body) = split(&buf).expect("a non-empty frame splits");
assert_eq!(Kind::from_byte(tag), Some(kind), "{kind:?} round-trips its tag");
assert_eq!(body, b"body");
}
let bytes = [Kind::Ops, Kind::Hello, Kind::Ping, Kind::Pong].map(Kind::as_byte);
assert_eq!(bytes, [0, 1, 2, 3], "wire values are pinned; renumbering breaks every peer");
}
#[test]
fn an_undeclared_version_agrees_with_everything() {
assert!(ProtocolVersion::UNKNOWN.agrees_with(ProtocolVersion(7)));
assert!(ProtocolVersion(7).agrees_with(ProtocolVersion::UNKNOWN));
assert!(ProtocolVersion(7).agrees_with(ProtocolVersion(7)));
assert!(!ProtocolVersion(7).agrees_with(ProtocolVersion(8)));
}
#[test]
fn an_empty_frame_is_malformed() {
assert_eq!(split(&[]), None);
}
#[cfg(feature = "json")]
mod probes {
use super::*;
use crate::{JsonCodec, WireCodec};
#[test]
fn a_pong_echoes_the_origin_it_was_given() {
let mut ping = Vec::new();
begin(Kind::Ping, &mut ping);
JsonCodec.encode_into(&Ping { origin: 987_654_321 }, &mut ping).unwrap();
let (_, body) = split(&ping).unwrap();
let reply = answer_ping(&JsonCodec, body, Some(42)).expect("a well-formed ping is answerable");
let (kind, body) = split(&reply).unwrap();
assert_eq!(Kind::from_byte(kind), Some(Kind::Pong));
let pong: Pong = JsonCodec.decode(body).unwrap();
assert_eq!(pong.origin, 987_654_321, "the origin comes back unread");
assert_eq!(pong.responder, Some(42));
}
#[test]
fn a_responder_without_a_clock_offers_nothing() {
let mut ping = Vec::new();
begin(Kind::Ping, &mut ping);
JsonCodec.encode_into(&Ping { origin: 1 }, &mut ping).unwrap();
let (_, body) = split(&ping).unwrap();
let reply = answer_ping(&JsonCodec, body, None).unwrap();
let (_, body) = split(&reply).unwrap();
assert_eq!(JsonCodec.decode::<Pong>(body).unwrap().responder, None);
}
#[test]
fn a_malformed_ping_is_unanswerable_rather_than_fatal() {
assert!(answer_ping(&JsonCodec, b"not a ping", None).is_none());
}
}
#[cfg(feature = "msgpack")]
#[test]
fn a_pong_without_a_clock_survives_msgpack() {
use crate::{MsgPackCodec, WireCodec};
let pong = Pong { origin: 7, responder: None };
let mut buf = Vec::new();
MsgPackCodec.encode_into(&pong, &mut buf).unwrap();
assert_eq!(MsgPackCodec.decode::<Pong>(&buf).unwrap(), pong);
}
#[test]
#[cfg(feature = "json")]
fn ops_ride_one_frame_and_other_kinds_decode_to_none() {
use crate::JsonCodec;
let frame = encode_ops(&JsonCodec, &["a", "b"]).unwrap();
assert_eq!(frame[0], Kind::Ops.as_byte());
assert_eq!(decode_ops::<_, String>(&JsonCodec, &frame), Some(vec!["a".to_owned(), "b".to_owned()]));
let mut hello = Vec::new();
begin(Kind::Hello, &mut hello);
assert_eq!(decode_ops::<_, String>(&JsonCodec, &hello), None, "not ops, not an error");
}
}