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//! [`ChannelData`] is used as an optional more efficient data transfer mechanism between a TURN
12//! server and a TURN client. A [`ChannelData`] message contains a simple 4-byte header that
13//! contains the channel identifier and the length of the data.
14
15use stun_types::message::StunParseError;
16
17/// A [`ChannelData`] message.
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub struct ChannelData<'a> {
20    id: u16,
21    data: &'a [u8],
22}
23
24impl<'a> ChannelData<'a> {
25    /// Construct a new [`ChannelData`] with the provided identifer and byte sequence.
26    pub fn new(id: u16, data: &'a [u8]) -> Self {
27        Self { id, data }
28    }
29
30    /// The channel identifier stored in this piece of data.
31    pub fn id(&self) -> u16 {
32        self.id
33    }
34
35    /// The sequence of bytes in this message.
36    pub fn data(&self) -> &[u8] {
37        self.data
38    }
39
40    /// Parse a sequence of bytes into a [`ChannelData`].  Returns appropriate errors on failure.
41    ///
42    /// # Examples
43    /// ```
44    /// # use turn_types::channel::*;
45    /// let data = [4; 3];
46    /// let channel = ChannelData::new(0x4000, &data);
47    /// let mut output = [0; 7];
48    /// assert_eq!(7, channel.write_into_unchecked(&mut output));
49    /// let parsed = ChannelData::parse(&output).unwrap();
50    /// assert_eq!(parsed.id(), channel.id());
51    /// assert_eq!(parsed.data(), channel.data());
52    /// ```
53    pub fn parse(data: &'a [u8]) -> Result<Self, StunParseError> {
54        let (id, len) = Self::parse_header(data)?;
55
56        if len + 4 > data.len() {
57            return Err(stun_types::message::StunParseError::Truncated {
58                expected: 4 + len,
59                actual: data.len(),
60            });
61        }
62
63        Ok(ChannelData {
64            id,
65            data: &data[4..4 + len],
66        })
67    }
68
69    /// Parse the header of an [`ChannelData`] returning the channel ID and the length of the
70    /// contained data (without the 4 byte header).
71    ///
72    /// # Examples
73    /// ```
74    /// # use turn_types::channel::*;
75    /// let data = [4; 3];
76    /// let channel = ChannelData::new(0x4000, &data);
77    /// let mut output = [0; 7];
78    /// assert_eq!(7, channel.write_into_unchecked(&mut output));
79    /// let (id, len) = ChannelData::parse_header(&output).unwrap();
80    /// assert_eq!(id, channel.id());
81    /// assert_eq!(len, 3);
82    /// ```
83    pub fn parse_header(data: &[u8]) -> Result<(u16, usize), StunParseError> {
84        if data.len() < 4 {
85            return Err(stun_types::message::StunParseError::Truncated {
86                expected: 4,
87                actual: data.len(),
88            });
89        }
90        let id = u16::from_be_bytes([data[0], data[1]]);
91        let len = u16::from_be_bytes([data[2], data[3]]) as usize;
92
93        if !(0x4000..=0xFFFE).contains(&id) {
94            return Err(stun_types::message::StunParseError::InvalidAttributeData);
95        }
96
97        Ok((id, len))
98    }
99
100    /// Write this [`ChannelData`] into the provided destination slice.
101    ///
102    /// The destination slice must have size `ChannelData::data().len() + 4`.
103    pub fn write_into_unchecked(self, dest: &mut [u8]) -> usize {
104        dest[..2].copy_from_slice(self.id.to_be_bytes().as_ref());
105        dest[2..4].copy_from_slice((self.data.len() as u16).to_be_bytes().as_ref());
106        dest[4..].copy_from_slice(self.data);
107        self.data.len() + 4
108    }
109}
110
111impl core::fmt::Display for ChannelData<'_> {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        write!(
114            f,
115            "ChannelData(id: {}, data of {} bytes)",
116            self.id,
117            self.data.len()
118        )
119    }
120}
121
122impl AsRef<[u8]> for ChannelData<'_> {
123    fn as_ref(&self) -> &[u8] {
124        self.data
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn channel_data_parse_invalid_id() {
134        let data = [0x00, 0x00, 0x00, 0x00];
135        assert!(matches!(
136            ChannelData::parse(&data),
137            Err(StunParseError::InvalidAttributeData)
138        ));
139    }
140
141    #[test]
142    fn channel_data_parse_empty() {
143        let data = [0x40, 0x00, 0x00, 0x00];
144        let channel = ChannelData::parse(&data).unwrap();
145        assert_eq!(channel.data(), &[]);
146    }
147
148    #[test]
149    fn channel_data_parse_truncated_data() {
150        let data = [0x40, 0x00, 0x00, 0x01];
151        let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
152            unreachable!();
153        };
154        assert_eq!(expected, 5);
155        assert_eq!(actual, 4);
156        assert_eq!(ChannelData::parse_header(&data).unwrap(), (0x4000, 1));
157    }
158
159    #[test]
160    fn channel_data_parse_truncated_header() {
161        let data = [0x40, 0x00, 0x00];
162        let Err(StunParseError::Truncated { expected, actual }) = ChannelData::parse(&data) else {
163            unreachable!();
164        };
165        assert_eq!(expected, 4);
166        assert_eq!(actual, 3);
167    }
168}