use crate::derp::Error;
pub const HEADER_LEN: usize = 5;
pub const MAX_FRAME: usize = 32 + wirefeather::MAX_DATAGRAM_LEN;
pub const MAX_WIRE_FRAME: usize = 64 * 1024;
pub const PING_LEN: usize = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameType {
ServerKey,
ClientInfo,
ServerInfo,
SendPacket,
RecvPacket,
KeepAlive,
NotePreferred,
PeerGone,
PeerPresent,
ForwardPacket,
WatchConns,
ClosePeer,
Ping,
Pong,
Health,
Restarting,
Other(u8),
}
impl FrameType {
pub fn from_byte(byte: u8) -> Self {
match byte {
0x01 => Self::ServerKey,
0x02 => Self::ClientInfo,
0x03 => Self::ServerInfo,
0x04 => Self::SendPacket,
0x05 => Self::RecvPacket,
0x06 => Self::KeepAlive,
0x07 => Self::NotePreferred,
0x08 => Self::PeerGone,
0x09 => Self::PeerPresent,
0x0a => Self::ForwardPacket,
0x10 => Self::WatchConns,
0x11 => Self::ClosePeer,
0x12 => Self::Ping,
0x13 => Self::Pong,
0x14 => Self::Health,
0x15 => Self::Restarting,
other => Self::Other(other),
}
}
pub fn to_byte(self) -> u8 {
match self {
Self::ServerKey => 0x01,
Self::ClientInfo => 0x02,
Self::ServerInfo => 0x03,
Self::SendPacket => 0x04,
Self::RecvPacket => 0x05,
Self::KeepAlive => 0x06,
Self::NotePreferred => 0x07,
Self::PeerGone => 0x08,
Self::PeerPresent => 0x09,
Self::ForwardPacket => 0x0a,
Self::WatchConns => 0x10,
Self::ClosePeer => 0x11,
Self::Ping => 0x12,
Self::Pong => 0x13,
Self::Health => 0x14,
Self::Restarting => 0x15,
Self::Other(other) => other,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Frame {
pub kind: FrameType,
pub length: usize,
}
impl Frame {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
let bytes = bytes.get(..HEADER_LEN).ok_or(Error::Incomplete)?;
let length = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
if length > MAX_WIRE_FRAME {
return Err(Error::TooLarge);
}
Ok(Self {
kind: FrameType::from_byte(bytes[0]),
length,
})
}
pub fn fits(&self) -> bool {
self.length <= MAX_FRAME
}
}
pub fn write(kind: FrameType, payload: &[u8], out: &mut [u8]) -> Result<usize, Error> {
let total = HEADER_LEN + payload.len();
let out = out.get_mut(..total).ok_or(Error::BufferTooSmall)?;
out[0] = kind.to_byte();
out[1..5].copy_from_slice(&(payload.len() as u32).to_be_bytes());
out[HEADER_LEN..].copy_from_slice(payload);
Ok(total)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_the_server_key_frame_a_real_server_sends() {
let bytes = [
0x01, 0x00, 0x00, 0x00, 0x28, 0x44, 0x45, 0x52, 0x50, 0xf0, 0x9f, 0x94, 0x91,
];
let frame = Frame::parse(&bytes).unwrap();
assert_eq!(frame.kind, FrameType::ServerKey);
assert_eq!(frame.length, 40, "8 bytes of magic and a 32-byte key");
assert_eq!(&bytes[HEADER_LEN..HEADER_LEN + 8], &crate::derp::MAGIC);
}
#[test]
fn the_length_is_big_endian_and_four_bytes() {
let frame = Frame::parse(&[0x04, 0x00, 0x00, 0x01, 0x2c]).unwrap();
assert_eq!(frame.length, 300);
assert_eq!(
Frame::parse(&[0x04, 0x2c, 0x01, 0x00, 0x00]),
Err(Error::TooLarge)
);
}
#[test]
fn a_frame_round_trips() {
let mut out = [0u8; 64];
let len = write(FrameType::SendPacket, b"payload", &mut out).unwrap();
assert_eq!(len, HEADER_LEN + 7);
let frame = Frame::parse(&out).unwrap();
assert_eq!(frame.kind, FrameType::SendPacket);
assert_eq!(frame.length, 7);
assert_eq!(&out[HEADER_LEN..len], b"payload");
}
#[test]
fn an_unknown_frame_type_is_carried_rather_than_refused() {
let frame = Frame::parse(&[0x63, 0, 0, 0, 0]).unwrap();
assert_eq!(frame.kind, FrameType::Other(0x63));
}
#[test]
fn a_large_but_legal_frame_is_skipped_rather_than_fatal() {
let mut header = [0u8; HEADER_LEN];
header[0] = FrameType::Health.to_byte();
header[1..].copy_from_slice(&(4096u32).to_be_bytes());
let frame = Frame::parse(&header).unwrap();
assert_eq!(frame.length, 4096);
assert!(!frame.fits(), "too big to keep, still readable");
let mut absurd = [0u8; HEADER_LEN];
absurd[1..].copy_from_slice(&((MAX_WIRE_FRAME + 1) as u32).to_be_bytes());
assert_eq!(Frame::parse(&absurd), Err(Error::TooLarge));
}
#[test]
fn the_frame_alphabet_matches_the_wire_numbering() {
for (byte, kind) in [
(0x09, FrameType::PeerPresent),
(0x0a, FrameType::ForwardPacket),
(0x10, FrameType::WatchConns),
(0x11, FrameType::ClosePeer),
(0x12, FrameType::Ping),
(0x13, FrameType::Pong),
(0x14, FrameType::Health),
(0x15, FrameType::Restarting),
] {
assert_eq!(FrameType::from_byte(byte), kind);
assert_eq!(kind.to_byte(), byte);
}
assert_eq!(FrameType::from_byte(0x0f), FrameType::Other(0x0f));
}
#[test]
fn a_short_header_is_incomplete_and_an_over_long_frame_is_refused() {
assert_eq!(Frame::parse(&[0x01, 0x00]), Err(Error::Incomplete));
assert_eq!(
Frame::parse(&[0x01, 0xff, 0xff, 0xff, 0xff]),
Err(Error::TooLarge)
);
let mut tiny = [0u8; 4];
assert_eq!(
write(FrameType::KeepAlive, b"x", &mut tiny),
Err(Error::BufferTooSmall)
);
}
}