1use std::fmt;
34
35#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
37pub struct Header(u32);
38
39impl Header {
40 pub const SIZE: usize = 4;
41
42 pub fn data(len: u16) -> Self {
44 Self(len as u32)
45 }
46
47 pub fn ping(len: u16) -> Self {
49 Self(0x1000000 | len as u32)
50 }
51
52 pub fn pong(len: u16) -> Self {
54 Self(0x2000000 | len as u32)
55 }
56
57 pub fn frame_type(self) -> Result<Type, u8> {
59 match (self.0 & 0xF000000) >> 24 {
60 0 => Ok(Type::Data),
61 1 => Ok(Type::Ping),
62 2 => Ok(Type::Pong),
63 t => Err(t as u8),
64 }
65 }
66
67 pub fn partial(self) -> Self {
69 Self(self.0 | 0x800000)
70 }
71
72 pub fn is_data(self) -> bool {
74 self.0 & 0xF000000 == 0
75 }
76
77 pub fn is_ping(self) -> bool {
79 self.0 & 0xF000000 == 0x1000000
80 }
81
82 pub fn is_pong(self) -> bool {
84 self.0 & 0xF000000 == 0x2000000
85 }
86
87 pub fn is_partial(self) -> bool {
89 self.0 & 0x800000 == 0x800000
90 }
91
92 pub fn len(self) -> u16 {
94 (self.0 & 0xFFFF) as u16
95 }
96
97 pub fn to_bytes(self) -> [u8; Self::SIZE] {
99 self.0.to_be_bytes()
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum Type {
106 Data,
107 Ping,
108 Pong,
109}
110
111impl From<Header> for [u8; Header::SIZE] {
112 fn from(val: Header) -> Self {
113 val.to_bytes()
114 }
115}
116
117impl TryFrom<&[u8]> for Header {
118 type Error = InvalidHeader;
119
120 fn try_from(val: &[u8]) -> Result<Self, Self::Error> {
121 let n = <[u8; Self::SIZE]>::try_from(val)
122 .map_err(|_| InvalidHeader("4-byte slice required"))?;
123 Ok(Self(u32::from_be_bytes(n)))
124 }
125}
126
127impl TryFrom<[u8; Header::SIZE]> for Header {
128 type Error = InvalidHeader;
129
130 fn try_from(val: [u8; Self::SIZE]) -> Result<Self, Self::Error> {
131 Ok(Self(u32::from_be_bytes(val)))
132 }
133}
134
135impl fmt::Display for Header {
136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137 f.debug_struct("Header")
138 .field("type", &self.frame_type())
139 .field("len", &self.len())
140 .field("partial", &self.is_partial())
141 .finish()
142 }
143}
144
145#[derive(Debug, thiserror::Error)]
146#[error("invalid header: {0}")]
147pub struct InvalidHeader(&'static str);
148
149#[cfg(test)]
150mod tests {
151 use quickcheck::quickcheck;
152
153 use super::{Header, Type};
154
155 quickcheck! {
156 fn data(len: u16) -> bool {
157 let hdr = Header::data(len);
158 hdr.is_data() && !hdr.is_partial() && hdr.frame_type() == Ok(Type::Data)
159 }
160
161 fn ping(len: u16) -> bool {
162 let hdr = Header::ping(len);
163 hdr.is_ping() && !hdr.is_partial() && hdr.frame_type() == Ok(Type::Ping)
164 }
165
166 fn pong(len: u16) -> bool {
167 let hdr = Header::pong(len);
168 hdr.is_pong() && !hdr.is_partial() && hdr.frame_type() == Ok(Type::Pong)
169 }
170
171 fn partial_data(len: u16) -> bool {
172 Header::data(len).partial().is_partial()
173 }
174
175 fn partial_ping(len: u16) -> bool {
176 Header::ping(len).partial().is_partial()
177 }
178
179 fn partial_pong(len: u16) -> bool {
180 Header::pong(len).partial().is_partial()
181 }
182
183 fn data_len(len: u16) -> bool {
184 Header::data(len).len() == len
185 }
186
187 fn ping_len(len: u16) -> bool {
188 Header::ping(len).len() == len
189 }
190
191 fn pong_len(len: u16) -> bool {
192 Header::pong(len).len() == len
193 }
194 }
195}