1use thiserror::Error;
22
23pub const HEADER_LEN: usize = 6;
26
27pub const MAX_PAYLOAD_LEN: u32 = 0x7f_ffff;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
34pub enum FramingError {
35 #[error("declared payload length {declared} exceeds maximum {max}")]
38 PayloadTooLarge { declared: u32, max: u32 },
39
40 #[error("unknown TCP message type code {0}")]
42 UnknownMessageType(u16),
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct Frame<'a> {
52 pub message_type: u16,
53 pub payload: &'a [u8],
54}
55
56impl Frame<'_> {
57 pub fn total_len(&self) -> usize {
60 HEADER_LEN + self.payload.len()
61 }
62}
63
64pub fn parse_frame(buf: &[u8]) -> Result<Option<Frame<'_>>, FramingError> {
73 let header = match buf.get(..HEADER_LEN) {
74 Some(header) => header,
75 None => return Ok(None),
76 };
77
78 let message_type = u16::from_be_bytes([header[0], header[1]]);
80 let declared = u32::from_be_bytes([header[2], header[3], header[4], header[5]]);
81
82 if declared > MAX_PAYLOAD_LEN {
83 return Err(FramingError::PayloadTooLarge {
84 declared,
85 max: MAX_PAYLOAD_LEN,
86 });
87 }
88
89 let payload_len = usize::try_from(declared).map_err(|_| FramingError::PayloadTooLarge {
92 declared,
93 max: MAX_PAYLOAD_LEN,
94 })?;
95 let end = HEADER_LEN
96 .checked_add(payload_len)
97 .ok_or(FramingError::PayloadTooLarge {
98 declared,
99 max: MAX_PAYLOAD_LEN,
100 })?;
101
102 match buf.get(HEADER_LEN..end) {
103 Some(payload) => Ok(Some(Frame {
104 message_type,
105 payload,
106 })),
107 None => Ok(None),
108 }
109}
110
111pub fn write_frame(
117 message_type: u16,
118 payload: &[u8],
119 out: &mut Vec<u8>,
120) -> Result<(), FramingError> {
121 let declared = u32::try_from(payload.len()).map_err(|_| FramingError::PayloadTooLarge {
122 declared: MAX_PAYLOAD_LEN.saturating_add(1),
123 max: MAX_PAYLOAD_LEN,
124 })?;
125 if declared > MAX_PAYLOAD_LEN {
126 return Err(FramingError::PayloadTooLarge {
127 declared,
128 max: MAX_PAYLOAD_LEN,
129 });
130 }
131 out.extend_from_slice(&message_type.to_be_bytes());
132 out.extend_from_slice(&declared.to_be_bytes());
133 out.extend_from_slice(payload);
134 Ok(())
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[repr(u16)]
144pub enum TcpMessageType {
145 Version = 0,
146 UdpTunnel = 1,
149 Authenticate = 2,
150 Ping = 3,
151 Reject = 4,
152 ServerSync = 5,
153 ChannelRemove = 6,
154 ChannelState = 7,
155 UserRemove = 8,
156 UserState = 9,
157 BanList = 10,
158 TextMessage = 11,
159 PermissionDenied = 12,
160 Acl = 13,
161 QueryUsers = 14,
162 CryptSetup = 15,
163 ContextActionModify = 16,
164 ContextAction = 17,
165 UserList = 18,
166 VoiceTarget = 19,
167 PermissionQuery = 20,
168 CodecVersion = 21,
169 UserStats = 22,
170 RequestBlob = 23,
171 ServerConfig = 24,
172 SuggestConfig = 25,
173 PluginDataTransmission = 26,
174}
175
176impl TryFrom<u16> for TcpMessageType {
177 type Error = FramingError;
178
179 fn try_from(value: u16) -> Result<Self, Self::Error> {
180 Ok(match value {
181 0 => Self::Version,
182 1 => Self::UdpTunnel,
183 2 => Self::Authenticate,
184 3 => Self::Ping,
185 4 => Self::Reject,
186 5 => Self::ServerSync,
187 6 => Self::ChannelRemove,
188 7 => Self::ChannelState,
189 8 => Self::UserRemove,
190 9 => Self::UserState,
191 10 => Self::BanList,
192 11 => Self::TextMessage,
193 12 => Self::PermissionDenied,
194 13 => Self::Acl,
195 14 => Self::QueryUsers,
196 15 => Self::CryptSetup,
197 16 => Self::ContextActionModify,
198 17 => Self::ContextAction,
199 18 => Self::UserList,
200 19 => Self::VoiceTarget,
201 20 => Self::PermissionQuery,
202 21 => Self::CodecVersion,
203 22 => Self::UserStats,
204 23 => Self::RequestBlob,
205 24 => Self::ServerConfig,
206 25 => Self::SuggestConfig,
207 26 => Self::PluginDataTransmission,
208 other => return Err(FramingError::UnknownMessageType(other)),
209 })
210 }
211}
212
213impl From<TcpMessageType> for u16 {
214 fn from(value: TcpMessageType) -> Self {
215 value as u16
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 #![allow(clippy::expect_used)]
225
226 use super::*;
227
228 #[test]
229 fn empty_buffer_needs_more() {
230 assert_eq!(parse_frame(&[]).expect("no error"), None);
231 }
232
233 #[test]
234 fn partial_header_needs_more() {
235 assert_eq!(parse_frame(&[0, 1, 0, 0, 0]).expect("no error"), None);
237 }
238
239 #[test]
240 fn partial_payload_needs_more() {
241 let buf = [0x00, 0x09, 0x00, 0x00, 0x00, 0x04, 0xAA, 0xBB];
243 assert_eq!(parse_frame(&buf).expect("no error"), None);
244 }
245
246 #[test]
247 fn parses_complete_frame() {
248 let buf = [0x00, 0x09, 0x00, 0x00, 0x00, 0x03, 1, 2, 3];
250 let frame = parse_frame(&buf).expect("no error").expect("a frame");
251 assert_eq!(frame.message_type, 9);
252 assert_eq!(frame.payload, &[1, 2, 3]);
253 assert_eq!(frame.total_len(), 9);
254 }
255
256 #[test]
257 fn parses_empty_payload_frame() {
258 let buf = [0x00, 0x03, 0x00, 0x00, 0x00, 0x00];
260 let frame = parse_frame(&buf).expect("no error").expect("a frame");
261 assert_eq!(frame.message_type, 3);
262 assert_eq!(frame.payload, &[] as &[u8]);
263 assert_eq!(frame.total_len(), HEADER_LEN);
264 }
265
266 #[test]
267 fn ignores_trailing_bytes_of_next_frame() {
268 let mut buf = Vec::new();
270 write_frame(7, &[0xDE, 0xAD], &mut buf).expect("write");
271 buf.extend_from_slice(&[0x00, 0x05]); let frame = parse_frame(&buf).expect("no error").expect("a frame");
273 assert_eq!(frame.message_type, 7);
274 assert_eq!(frame.payload, &[0xDE, 0xAD]);
275 assert_eq!(frame.total_len(), 8);
276 }
277
278 #[test]
279 fn rejects_oversized_payload() {
280 let declared = MAX_PAYLOAD_LEN + 1;
282 let mut buf = vec![0x00, 0x0B];
283 buf.extend_from_slice(&declared.to_be_bytes());
284 assert_eq!(
285 parse_frame(&buf),
286 Err(FramingError::PayloadTooLarge {
287 declared,
288 max: MAX_PAYLOAD_LEN,
289 })
290 );
291 }
292
293 #[test]
294 fn accepts_max_length_header() {
295 let mut buf = vec![0x00, 0x0B];
298 buf.extend_from_slice(&MAX_PAYLOAD_LEN.to_be_bytes());
299 assert_eq!(parse_frame(&buf).expect("not too large"), None);
300 }
301
302 #[test]
303 fn incremental_growth_yields_frame_once_complete() {
304 let full = {
305 let mut buf = Vec::new();
306 write_frame(2, &[1, 2, 3, 4, 5], &mut buf).expect("write");
307 buf
308 };
309 for len in 0..full.len() {
311 assert_eq!(
312 parse_frame(&full[..len]).expect("no error"),
313 None,
314 "len {len}"
315 );
316 }
317 let frame = parse_frame(&full).expect("no error").expect("a frame");
318 assert_eq!(frame.message_type, 2);
319 assert_eq!(frame.payload, &[1, 2, 3, 4, 5]);
320 }
321
322 #[test]
323 fn drains_multiple_frames_in_sequence() {
324 let mut buf = Vec::new();
325 write_frame(0, b"ver", &mut buf).expect("write");
326 write_frame(3, b"", &mut buf).expect("write");
327 write_frame(9, b"user", &mut buf).expect("write");
328
329 let mut cursor = &buf[..];
330 let mut seen = Vec::new();
331 while let Some(frame) = parse_frame(cursor).expect("no error") {
332 seen.push((frame.message_type, frame.payload.to_vec()));
333 cursor = &cursor[frame.total_len()..];
334 }
335 assert_eq!(
336 seen,
337 vec![
338 (0u16, b"ver".to_vec()),
339 (3, b"".to_vec()),
340 (9, b"user".to_vec()),
341 ]
342 );
343 assert!(cursor.is_empty());
344 }
345
346 #[test]
347 fn roundtrip_write_then_parse() {
348 for message_type in 0u16..=26 {
349 let payload: Vec<u8> = (0..message_type as usize).map(|i| i as u8).collect();
350 let mut buf = Vec::new();
351 write_frame(message_type, &payload, &mut buf).expect("write");
352 let frame = parse_frame(&buf).expect("no error").expect("a frame");
353 assert_eq!(frame.message_type, message_type);
354 assert_eq!(frame.payload, &payload[..]);
355 }
356 }
357
358 #[test]
359 fn all_known_type_codes_resolve() {
360 for code in 0u16..=26 {
361 let resolved = TcpMessageType::try_from(code).expect("known code");
362 assert_eq!(u16::from(resolved), code);
363 }
364 }
365
366 #[test]
367 fn unknown_type_code_is_rejected() {
368 assert_eq!(
369 TcpMessageType::try_from(27),
370 Err(FramingError::UnknownMessageType(27))
371 );
372 assert_eq!(
373 TcpMessageType::try_from(u16::MAX),
374 Err(FramingError::UnknownMessageType(u16::MAX))
375 );
376 }
377
378 #[test]
379 fn udp_tunnel_is_type_one() {
380 assert_eq!(u16::from(TcpMessageType::UdpTunnel), 1);
383 assert_eq!(
384 TcpMessageType::try_from(1).expect("known"),
385 TcpMessageType::UdpTunnel
386 );
387 }
388}