use core::fmt;
use crate::error::WireError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum FrameType {
Data = 0x00,
HandshakeInit = 0x01,
HandshakeReply = 0x02,
HandshakeComplete = 0x03,
Ping = 0x04,
Pong = 0x05,
Close = 0x06,
FileHeader = 0x07,
FileChunk = 0x08,
Ack = 0x09,
}
impl FrameType {
#[inline]
pub const fn from_u8(value: u8) -> Result<Self, WireError> {
match value {
0x00 => Ok(Self::Data),
0x01 => Ok(Self::HandshakeInit),
0x02 => Ok(Self::HandshakeReply),
0x03 => Ok(Self::HandshakeComplete),
0x04 => Ok(Self::Ping),
0x05 => Ok(Self::Pong),
0x06 => Ok(Self::Close),
0x07 => Ok(Self::FileHeader),
0x08 => Ok(Self::FileChunk),
0x09 => Ok(Self::Ack),
other => Err(WireError::UnknownFrameType(other)),
}
}
#[inline]
pub const fn is_handshake(&self) -> bool {
matches!(self, Self::HandshakeInit | Self::HandshakeReply | Self::HandshakeComplete)
}
#[inline]
pub const fn is_control(&self) -> bool {
matches!(self, Self::Ping | Self::Pong | Self::Close)
}
#[inline]
pub const fn is_file_transfer(&self) -> bool {
matches!(self, Self::FileHeader | Self::FileChunk)
}
}
impl fmt::Display for FrameType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Data => write!(f, "DATA"),
Self::HandshakeInit => write!(f, "HS_INIT"),
Self::HandshakeReply => write!(f, "HS_REPLY"),
Self::HandshakeComplete => write!(f, "HS_COMPLETE"),
Self::Ping => write!(f, "PING"),
Self::Pong => write!(f, "PONG"),
Self::Close => write!(f, "CLOSE"),
Self::FileHeader => write!(f, "FILE_HDR"),
Self::FileChunk => write!(f, "FILE_CHUNK"),
Self::Ack => write!(f, "ACK"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_types_roundtrip() {
let types = [
(0x00, FrameType::Data),
(0x01, FrameType::HandshakeInit),
(0x02, FrameType::HandshakeReply),
(0x03, FrameType::HandshakeComplete),
(0x04, FrameType::Ping),
(0x05, FrameType::Pong),
(0x06, FrameType::Close),
(0x07, FrameType::FileHeader),
(0x08, FrameType::FileChunk),
(0x09, FrameType::Ack),
];
for (byte, expected) in types {
assert_eq!(FrameType::from_u8(byte), Ok(expected));
assert_eq!(expected as u8, byte);
}
}
#[test]
fn unknown_type_rejected() {
for byte in 0x0A..=0x0F {
assert!(matches!(
FrameType::from_u8(byte),
Err(WireError::UnknownFrameType(_))
));
}
}
#[test]
fn classification() {
assert!(FrameType::HandshakeInit.is_handshake());
assert!(FrameType::HandshakeReply.is_handshake());
assert!(FrameType::HandshakeComplete.is_handshake());
assert!(!FrameType::Data.is_handshake());
assert!(FrameType::Ping.is_control());
assert!(FrameType::Pong.is_control());
assert!(FrameType::Close.is_control());
assert!(!FrameType::Data.is_control());
assert!(FrameType::FileHeader.is_file_transfer());
assert!(FrameType::FileChunk.is_file_transfer());
assert!(!FrameType::Data.is_file_transfer());
}
}