1use core::fmt;
6use crate::error::WireError;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u8)]
11pub enum FrameType {
12 Data = 0x00,
14
15 HandshakeInit = 0x01,
17
18 HandshakeReply = 0x02,
20
21 HandshakeComplete = 0x03,
23
24 Ping = 0x04,
26
27 Pong = 0x05,
29
30 Close = 0x06,
32
33 FileHeader = 0x07,
35
36 FileChunk = 0x08,
38
39 Ack = 0x09,
41}
42
43impl FrameType {
44 #[inline]
46 pub const fn from_u8(value: u8) -> Result<Self, WireError> {
47 match value {
48 0x00 => Ok(Self::Data),
49 0x01 => Ok(Self::HandshakeInit),
50 0x02 => Ok(Self::HandshakeReply),
51 0x03 => Ok(Self::HandshakeComplete),
52 0x04 => Ok(Self::Ping),
53 0x05 => Ok(Self::Pong),
54 0x06 => Ok(Self::Close),
55 0x07 => Ok(Self::FileHeader),
56 0x08 => Ok(Self::FileChunk),
57 0x09 => Ok(Self::Ack),
58 other => Err(WireError::UnknownFrameType(other)),
59 }
60 }
61
62 #[inline]
64 pub const fn is_handshake(&self) -> bool {
65 matches!(self, Self::HandshakeInit | Self::HandshakeReply | Self::HandshakeComplete)
66 }
67
68 #[inline]
70 pub const fn is_control(&self) -> bool {
71 matches!(self, Self::Ping | Self::Pong | Self::Close)
72 }
73
74 #[inline]
76 pub const fn is_file_transfer(&self) -> bool {
77 matches!(self, Self::FileHeader | Self::FileChunk)
78 }
79}
80
81impl fmt::Display for FrameType {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Self::Data => write!(f, "DATA"),
85 Self::HandshakeInit => write!(f, "HS_INIT"),
86 Self::HandshakeReply => write!(f, "HS_REPLY"),
87 Self::HandshakeComplete => write!(f, "HS_COMPLETE"),
88 Self::Ping => write!(f, "PING"),
89 Self::Pong => write!(f, "PONG"),
90 Self::Close => write!(f, "CLOSE"),
91 Self::FileHeader => write!(f, "FILE_HDR"),
92 Self::FileChunk => write!(f, "FILE_CHUNK"),
93 Self::Ack => write!(f, "ACK"),
94 }
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101
102 #[test]
103 fn all_types_roundtrip() {
104 let types = [
105 (0x00, FrameType::Data),
106 (0x01, FrameType::HandshakeInit),
107 (0x02, FrameType::HandshakeReply),
108 (0x03, FrameType::HandshakeComplete),
109 (0x04, FrameType::Ping),
110 (0x05, FrameType::Pong),
111 (0x06, FrameType::Close),
112 (0x07, FrameType::FileHeader),
113 (0x08, FrameType::FileChunk),
114 (0x09, FrameType::Ack),
115 ];
116 for (byte, expected) in types {
117 assert_eq!(FrameType::from_u8(byte), Ok(expected));
118 assert_eq!(expected as u8, byte);
119 }
120 }
121
122 #[test]
123 fn unknown_type_rejected() {
124 for byte in 0x0A..=0x0F {
125 assert!(matches!(
126 FrameType::from_u8(byte),
127 Err(WireError::UnknownFrameType(_))
128 ));
129 }
130 }
131
132 #[test]
133 fn classification() {
134 assert!(FrameType::HandshakeInit.is_handshake());
135 assert!(FrameType::HandshakeReply.is_handshake());
136 assert!(FrameType::HandshakeComplete.is_handshake());
137 assert!(!FrameType::Data.is_handshake());
138
139 assert!(FrameType::Ping.is_control());
140 assert!(FrameType::Pong.is_control());
141 assert!(FrameType::Close.is_control());
142 assert!(!FrameType::Data.is_control());
143
144 assert!(FrameType::FileHeader.is_file_transfer());
145 assert!(FrameType::FileChunk.is_file_transfer());
146 assert!(!FrameType::Data.is_file_transfer());
147 }
148}