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