Skip to main content

turn_types/attribute/
connection.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};
12use stun_types::{attribute::*, message::StunParseError};
13
14/// The [`ConnectionId`] [`Attribute`].
15///
16/// TCP Connection handling through a TURN server.
17///
18/// Reference: [RFC6062 Section 6.2.1](https://datatracker.ietf.org/doc/html/rfc6062#section-6.2.1).
19#[derive(Debug, Clone)]
20pub struct ConnectionId {
21    id: u32,
22}
23
24impl AttributeStaticType for ConnectionId {
25    const TYPE: AttributeType = AttributeType::new(0x002a);
26}
27
28impl Attribute for ConnectionId {
29    fn get_type(&self) -> AttributeType {
30        Self::TYPE
31    }
32
33    fn length(&self) -> u16 {
34        4
35    }
36}
37
38impl AttributeWrite for ConnectionId {
39    fn to_raw(&self) -> RawAttribute<'_> {
40        let mut data = [0; 4];
41        BigEndian::write_u32(&mut data, self.id);
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_u32(&mut dest[4..8], self.id);
48    }
49}
50
51impl AttributeFromRaw<'_> for ConnectionId {
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 ConnectionId {
61    type Error = StunParseError;
62    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
63        raw.check_type_and_len(Self::TYPE, 4..=4)?;
64        Ok(Self {
65            id: BigEndian::read_u32(&raw.value[..4]),
66        })
67    }
68}
69
70impl ConnectionId {
71    /// Create a new [`ConnectionId`] [`Attribute`].
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// # use turn_types::attribute::*;
77    /// let conn_id = ConnectionId::new(0x42);
78    /// assert_eq!(conn_id.id(), 0x42);
79    /// ```
80    pub fn new(id: u32) -> Self {
81        Self { id }
82    }
83
84    /// Retrieve the protocol stored in a [`ConnectionId`]
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// # use turn_types::attribute::*;
90    /// let conn_id = ConnectionId::new(0x402);
91    /// assert_eq!(conn_id.id(), 0x402);
92    /// ```
93    pub fn id(&self) -> u32 {
94        self.id
95    }
96}
97
98impl core::fmt::Display for ConnectionId {
99    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100        write!(f, "{}: {:x?}", self.get_type(), self.id())
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use alloc::vec::Vec;
108    use byteorder::{BigEndian, ByteOrder};
109    use tracing::trace;
110
111    #[test]
112    fn requested_transport() {
113        let _log = crate::tests::test_init_log();
114        let trans = ConnectionId::new(17);
115        assert_eq!(trans.get_type(), ConnectionId::TYPE);
116        assert_eq!(trans.id(), 17);
117    }
118
119    #[test]
120    fn requested_transport_raw() {
121        let _log = crate::tests::test_init_log();
122        let trans = ConnectionId::new(17);
123        assert_eq!(trans.get_type(), ConnectionId::TYPE);
124        assert_eq!(trans.id(), 17);
125        let raw: RawAttribute = trans.to_raw();
126        trace!("raw: {raw:?}");
127        assert_eq!(raw.get_type(), ConnectionId::TYPE);
128        let trans2 = ConnectionId::try_from(&raw).unwrap();
129        assert_eq!(trans2.get_type(), ConnectionId::TYPE);
130        assert_eq!(trans2.id(), 17);
131    }
132
133    #[test]
134    fn requested_transport_raw_wrong_type() {
135        let _log = crate::tests::test_init_log();
136        let trans = ConnectionId::new(17);
137        assert_eq!(trans.get_type(), ConnectionId::TYPE);
138        assert_eq!(trans.id(), 17);
139        let raw: RawAttribute = trans.to_raw();
140        // provide incorrectly typed data
141        let mut data: Vec<_> = raw.into();
142        BigEndian::write_u16(&mut data[0..2], 0);
143        assert!(matches!(
144            ConnectionId::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
145            Err(StunParseError::WrongAttributeImplementation)
146        ));
147    }
148
149    #[test]
150    fn requested_transport_write_into() {
151        let _log = crate::tests::test_init_log();
152        let trans = ConnectionId::new(17);
153        assert_eq!(trans.get_type(), ConnectionId::TYPE);
154        assert_eq!(trans.id(), 17);
155        let mut data = [0; 8];
156        trans.write_into(&mut data).unwrap();
157        let raw = RawAttribute::from_bytes(&data).unwrap();
158        let trans2 = ConnectionId::from_raw_ref(&raw).unwrap();
159        assert_eq!(trans.id(), trans2.id());
160    }
161
162    #[test]
163    #[should_panic = "out of range"]
164    fn requested_transport_write_into_unchecked() {
165        let _log = crate::tests::test_init_log();
166        let trans = ConnectionId::new(17);
167        assert_eq!(trans.get_type(), ConnectionId::TYPE);
168        assert_eq!(trans.id(), 17);
169        let mut data = [0; 7];
170        trans.write_into_unchecked(&mut data);
171    }
172}