turn_types/
channel.rs

1// Copyright (C) 2025 Matthew Waters <matthew@centricular.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! TURN [`ChannelData`] messages.
10//!
11//! Used as an optional more efficient data transfer mechanism between a TURN server and a TURN
12//! client.
13
14use stun_types::message::StunParseError;
15
16/// A [`ChannelData`] message.
17#[derive(Debug, Copy, Clone)]
18pub struct ChannelData<'a> {
19    id: u16,
20    data: &'a [u8],
21}
22
23impl<'a> ChannelData<'a> {
24    /// Construct a new [`ChannelData`] with the provided identifer and byte sequence.
25    pub fn new(id: u16, data: &'a [u8]) -> Self {
26        Self { id, data }
27    }
28
29    /// The channel identifier stored in this piece of data.
30    pub fn id(&self) -> u16 {
31        self.id
32    }
33
34    /// The sequence of bytes in this message.
35    pub fn data(&self) -> &[u8] {
36        self.data
37    }
38
39    /// Parse a sequence of bytes into a [`ChannelData`].  Returns appropriate errors on failure.
40    ///
41    /// # Examples
42    /// ```
43    /// # use turn_types::channel::*;
44    /// let data = [4; 3];
45    /// let channel = ChannelData::new(0x4000, &data);
46    /// let mut output = [0; 7];
47    /// assert_eq!(7, channel.write_into_unchecked(&mut output));
48    /// let parsed = ChannelData::parse(&output).unwrap();
49    /// assert_eq!(parsed.id(), channel.id());
50    /// assert_eq!(parsed.data(), channel.data());
51    /// ```
52    pub fn parse(data: &'a [u8]) -> Result<Self, StunParseError> {
53        if data.len() < 4 {
54            return Err(stun_types::message::StunParseError::Truncated {
55                expected: 4,
56                actual: data.len(),
57            });
58        }
59        let id = u16::from_be_bytes([data[0], data[1]]);
60        let len = u16::from_be_bytes([data[2], data[3]]) as usize;
61        if len + 4 > data.len() {
62            return Err(stun_types::message::StunParseError::Truncated {
63                expected: 4 + len,
64                actual: data.len(),
65            });
66        }
67
68        if !(0x4000..=0xFFFE).contains(&id) {
69            return Err(stun_types::message::StunParseError::InvalidAttributeData);
70        }
71
72        Ok(ChannelData {
73            id,
74            data: &data[4..4 + len],
75        })
76    }
77
78    /// Write this [`ChannelData`] into the provided destination slice.
79    ///
80    /// The destination slice must have size `ChannelData::data().len() + 4`.
81    pub fn write_into_unchecked(self, dest: &mut [u8]) -> usize {
82        dest[..2].copy_from_slice(self.id.to_be_bytes().as_ref());
83        dest[2..4].copy_from_slice((self.data.len() as u16).to_be_bytes().as_ref());
84        dest[4..].copy_from_slice(self.data);
85        self.data.len() + 4
86    }
87}
88
89impl std::fmt::Display for ChannelData<'_> {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        write!(
92            f,
93            "ChannelData(id: {}, data of {} bytes)",
94            self.id,
95            self.data.len()
96        )
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn channel_data_parse_invalid_id() {
106        let data = [0x00, 0x00, 0x00, 0x00];
107        assert!(matches!(
108            ChannelData::parse(&data),
109            Err(StunParseError::InvalidAttributeData)
110        ));
111    }
112
113    #[test]
114    fn channel_data_parse_empty() {
115        let data = [0x40, 0x00, 0x00, 0x00];
116        let channel = ChannelData::parse(&data).unwrap();
117        assert_eq!(channel.data(), &[]);
118    }
119
120    #[test]
121    fn channel_data_parse_truncated_data() {
122        let data = [0x40, 0x00, 0x00, 0x01];
123        let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
124            unreachable!();
125        };
126        assert_eq!(expected, 5);
127        assert_eq!(actual, 4);
128    }
129
130    #[test]
131    fn channel_data_parse_truncated_header() {
132        let data = [0x40, 0x00, 0x00];
133        let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
134            unreachable!();
135        };
136        assert_eq!(expected, 4);
137        assert_eq!(actual, 3);
138    }
139}