Skip to main content

turn_types/attribute/
reservation.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// SPDX-License-Identifier: MIT OR Apache-2.0
10
11use byteorder::{BigEndian, ByteOrder};
12
13use stun_types::{attribute::*, message::StunParseError};
14
15/// The [`ReservationToken`] [`Attribute`].
16///
17/// Reserves an allocation on the TURN server or acquires an already existing allocation on the
18/// TURN server.
19///
20/// Reference: [RFC5766 Section 14.9](https://datatracker.ietf.org/doc/html/rfc5766#section-14.9).
21#[derive(Debug, Clone)]
22pub struct ReservationToken {
23    token: u64,
24}
25
26impl AttributeStaticType for ReservationToken {
27    const TYPE: AttributeType = AttributeType::new(0x0022);
28}
29
30impl Attribute for ReservationToken {
31    fn get_type(&self) -> AttributeType {
32        Self::TYPE
33    }
34
35    fn length(&self) -> u16 {
36        8
37    }
38}
39
40impl AttributeWrite for ReservationToken {
41    fn to_raw(&self) -> RawAttribute<'_> {
42        let mut data = [0; 8];
43        BigEndian::write_u64(&mut data, self.token);
44        RawAttribute::new(self.get_type(), &data).into_owned()
45    }
46
47    fn write_into_unchecked(&self, dest: &mut [u8]) {
48        self.write_header_unchecked(dest);
49        BigEndian::write_u64(&mut dest[4..12], self.token);
50    }
51}
52
53impl AttributeFromRaw<'_> for ReservationToken {
54    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
55    where
56        Self: Sized,
57    {
58        Self::try_from(raw)
59    }
60}
61
62impl TryFrom<&RawAttribute<'_>> for ReservationToken {
63    type Error = StunParseError;
64    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
65        raw.check_type_and_len(Self::TYPE, 8..=8)?;
66        Ok(Self {
67            token: BigEndian::read_u64(&raw.value),
68        })
69    }
70}
71
72impl ReservationToken {
73    /// Create a new [`ReservationToken`] [`Attribute`].
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// # use turn_types::attribute::*;
79    /// let token = ReservationToken::new(100);
80    /// assert_eq!(token.token(), 100);
81    /// ```
82    pub fn new(token: u64) -> Self {
83        Self { token }
84    }
85
86    /// Retrieve the token stored in a [`ReservationToken`].
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// # use turn_types::attribute::*;
92    /// let token = ReservationToken::new(100);
93    /// assert_eq!(token.token(), 100);
94    /// ```
95    pub fn token(&self) -> u64 {
96        self.token
97    }
98}
99
100impl core::fmt::Display for ReservationToken {
101    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102        write!(f, "{}: 0x{:#x}", self.get_type(), self.token())
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use alloc::{vec, vec::Vec};
110    use byteorder::{BigEndian, ByteOrder};
111    use tracing::trace;
112
113    #[test]
114    fn reservation_token() {
115        let _log = crate::tests::test_init_log();
116        let token = ReservationToken::new(200);
117        assert_eq!(token.get_type(), ReservationToken::TYPE);
118        assert_eq!(token.token(), 200);
119    }
120
121    #[test]
122    fn reservation_token_raw() {
123        let _log = crate::tests::test_init_log();
124        let token = ReservationToken::new(200);
125        let raw: RawAttribute = token.to_raw();
126        trace!("{}", raw);
127        assert_eq!(raw.get_type(), ReservationToken::TYPE);
128        let token2 = ReservationToken::try_from(&raw).unwrap();
129        assert_eq!(token2.get_type(), ReservationToken::TYPE);
130        assert_eq!(token2.token(), 200);
131    }
132
133    #[test]
134    fn reservation_token_raw_wrong_type() {
135        let _log = crate::tests::test_init_log();
136        let token = ReservationToken::new(200);
137        let raw: RawAttribute = token.to_raw();
138        // provide incorrectly typed data
139        let mut data: Vec<_> = raw.into();
140        BigEndian::write_u16(&mut data[0..2], 0);
141        assert!(matches!(
142            ReservationToken::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
143            Err(StunParseError::WrongAttributeImplementation)
144        ));
145    }
146
147    #[test]
148    fn reservation_token_write_into() {
149        let _log = crate::tests::test_init_log();
150        let token = ReservationToken::new(200);
151        let raw: RawAttribute = token.to_raw();
152
153        let mut dest = vec![0; raw.padded_len()];
154        token.write_into(&mut dest).unwrap();
155        let raw = RawAttribute::from_bytes(&dest).unwrap();
156        let token2 = ReservationToken::try_from(&raw).unwrap();
157        assert_eq!(token2.get_type(), ReservationToken::TYPE);
158        assert_eq!(token2.token(), 200);
159    }
160
161    #[test]
162    #[should_panic = "out of range"]
163    fn reservation_token_write_into_unchecked() {
164        let _log = crate::tests::test_init_log();
165        let token = ReservationToken::new(200);
166        let raw: RawAttribute = token.to_raw();
167
168        let mut dest = vec![0; raw.padded_len() - 1];
169        token.write_into_unchecked(&mut dest);
170    }
171}