cliquenet/
frame.rs

1//! # Frame header
2//!
3//! The unit of data exchanged over the network is called a `Frame` and consists of
4//! a 4-byte header and a body of variable size. The header has the following
5//! structure:
6//!
7//! ```text
8//!  0                   1                   2                   3
9//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
10//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
11//! |       |       |P|             |                               |
12//! |Version|  Type |a|  Reserved   |        Payload length         |
13//! |       |       |r|             |                               |
14//! |       |       |t|             |                               |
15//! +-------+-------+-+-------------+-------------------------------+
16//! ```
17//!
18//! where
19//!
20//! - Version (4 bits)
21//! - Type (4 bits)
22//!    - Data (0)
23//!    - Ping (1)
24//!    - Pong (2)
25//! - Partial (1 bit)
26//! - Reserved (7 bits)
27//! - Payload length (16 bits)
28//!
29//! If the partial bit is set, the frame is only a part of the message and the read task
30//! will assemble all frames to produce the final message. The maximum total message size
31//! is capped to 5 MiB.
32
33use std::fmt;
34
35/// The header of a frame.
36#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
37pub struct Header(u32);
38
39impl Header {
40    pub const SIZE: usize = 4;
41
42    /// Create a data header with the given payload length.
43    pub fn data(len: u16) -> Self {
44        Self(len as u32)
45    }
46
47    /// Create a ping header with the given payload length.
48    pub fn ping(len: u16) -> Self {
49        Self(0x1000000 | len as u32)
50    }
51
52    /// Create a pong header with the given payload length.
53    pub fn pong(len: u16) -> Self {
54        Self(0x2000000 | len as u32)
55    }
56
57    /// The type of the frame following this header.
58    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    /// Set the partial flag to indicate that more frames follow.
68    pub fn partial(self) -> Self {
69        Self(self.0 | 0x800000)
70    }
71
72    /// Is this a data frame header?
73    pub fn is_data(self) -> bool {
74        self.0 & 0xF000000 == 0
75    }
76
77    /// Is this a ping frame header?
78    pub fn is_ping(self) -> bool {
79        self.0 & 0xF000000 == 0x1000000
80    }
81
82    /// Is this a pong frame header?
83    pub fn is_pong(self) -> bool {
84        self.0 & 0xF000000 == 0x2000000
85    }
86
87    /// Is this a partial frame?
88    pub fn is_partial(self) -> bool {
89        self.0 & 0x800000 == 0x800000
90    }
91
92    /// Get the payload length.
93    pub fn len(self) -> u16 {
94        (self.0 & 0xFFFF) as u16
95    }
96
97    /// Convert this header into a byte array.
98    pub fn to_bytes(self) -> [u8; Self::SIZE] {
99        self.0.to_be_bytes()
100    }
101}
102
103/// The type of a frame.
104#[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}