Skip to main content

turn_types/attribute/
icmp.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 [`Icmp`] [`Attribute`].
15///
16/// Attribute used by TURN to forward ICMP packets towards the client.
17///
18/// Reference: [RFC8656 Section 18.13](https://datatracker.ietf.org/doc/html/rfc8656#section-18.13).
19#[derive(Debug, Clone)]
20pub struct Icmp {
21    typ: u8,
22    code: u8,
23    data: u32,
24}
25
26impl AttributeStaticType for Icmp {
27    const TYPE: AttributeType = AttributeType::new(0x8004);
28}
29
30impl Attribute for Icmp {
31    fn get_type(&self) -> AttributeType {
32        Self::TYPE
33    }
34
35    fn length(&self) -> u16 {
36        8
37    }
38}
39
40impl AttributeWrite for Icmp {
41    fn to_raw(&self) -> RawAttribute<'_> {
42        let mut data = [0; 8];
43        data[2] = self.typ;
44        data[3] = self.code;
45        BigEndian::write_u32(&mut data[4..8], self.data);
46        RawAttribute::new(Self::TYPE, &data).into_owned()
47    }
48    fn write_into_unchecked(&self, dest: &mut [u8]) {
49        self.write_header_unchecked(dest);
50        dest[6] = self.typ;
51        dest[7] = self.code;
52        BigEndian::write_u32(&mut dest[8..12], self.data);
53    }
54}
55
56impl AttributeFromRaw<'_> for Icmp {
57    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
58    where
59        Self: Sized,
60    {
61        Self::try_from(raw)
62    }
63}
64
65impl TryFrom<&RawAttribute<'_>> for Icmp {
66    type Error = StunParseError;
67    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
68        raw.check_type_and_len(Self::TYPE, 8..=8)?;
69        let typ = raw.value[2];
70        let code = raw.value[3];
71        let data = BigEndian::read_u32(&raw.value[4..8]);
72        Ok(Self { typ, code, data })
73    }
74}
75
76impl Icmp {
77    /// Create a new [`Icmp`] [`Attribute`].
78    ///
79    /// # Examples
80    ///
81    /// ```
82    /// # use turn_types::attribute::*;
83    /// let icmp = Icmp::new(1, 2, 3);
84    /// assert_eq!(icmp.icmp_type(), 1);
85    /// assert_eq!(icmp.code(), 2);
86    /// assert_eq!(icmp.data(), 3);
87    /// ```
88    pub fn new(typ: u8, code: u8, data: u32) -> Self {
89        Self { typ, code, data }
90    }
91
92    /// Retrieve the type of the ICMP stored in a [`Icmp`].
93    pub fn icmp_type(&self) -> u8 {
94        self.typ
95    }
96
97    /// Retrieve the code of the ICMP stored in a [`Icmp`].
98    pub fn code(&self) -> u8 {
99        self.code
100    }
101
102    /// Retrieve any additional data of the ICMP stored in a [`Icmp`].
103    pub fn data(&self) -> u32 {
104        self.data
105    }
106}
107
108impl core::fmt::Display for Icmp {
109    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
110        write!(
111            f,
112            "{}: type:{}, code:{}, data:{}",
113            self.get_type(),
114            self.typ,
115            self.code,
116            self.data
117        )
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use alloc::{vec, vec::Vec};
125    use tracing::trace;
126
127    #[test]
128    fn icmp() {
129        let _log = crate::tests::test_init_log();
130        let mapped = Icmp::new(2, 4, 8);
131        assert_eq!(mapped.get_type(), Icmp::TYPE);
132        assert_eq!(mapped.icmp_type(), 2);
133        assert_eq!(mapped.code(), 4);
134        assert_eq!(mapped.data(), 8);
135    }
136
137    #[test]
138    fn icmp_raw() {
139        let _log = crate::tests::test_init_log();
140        let mapped = Icmp::new(2, 4, 8);
141        let raw: RawAttribute = mapped.to_raw();
142        trace!("{}", raw);
143        assert_eq!(raw.get_type(), Icmp::TYPE);
144        let mapped2 = Icmp::try_from(&raw).unwrap();
145        assert_eq!(mapped2.get_type(), Icmp::TYPE);
146        assert_eq!(mapped2.icmp_type(), 2);
147        assert_eq!(mapped2.code(), 4);
148        assert_eq!(mapped2.data(), 8);
149    }
150
151    #[test]
152    fn icmp_raw_short() {
153        let _log = crate::tests::test_init_log();
154        let mapped = Icmp::new(2, 4, 8);
155        let raw: RawAttribute = mapped.to_raw();
156        assert_eq!(raw.get_type(), Icmp::TYPE);
157        // truncate by one byte
158        let mut data: Vec<_> = raw.clone().into();
159        let len = data.len();
160        BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
161        assert!(matches!(
162            Icmp::try_from(&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()),
163            Err(StunParseError::Truncated {
164                expected: _,
165                actual: _,
166            })
167        ));
168    }
169
170    #[test]
171    fn icmp_raw_wrong_type() {
172        let _log = crate::tests::test_init_log();
173        let mapped = Icmp::new(2, 4, 8);
174        let raw: RawAttribute = mapped.to_raw();
175        assert_eq!(raw.get_type(), Icmp::TYPE);
176        // provide incorrectly typed data
177        let mut data: Vec<_> = raw.clone().into();
178        BigEndian::write_u16(&mut data[0..2], 0);
179        assert!(matches!(
180            Icmp::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
181            Err(StunParseError::WrongAttributeImplementation)
182        ));
183    }
184
185    #[test]
186    fn icmp_write_into() {
187        let _log = crate::tests::test_init_log();
188        let mapped = Icmp::new(2, 4, 8);
189        let raw: RawAttribute = mapped.to_raw();
190        let mut dest = vec![0; raw.padded_len()];
191        mapped.write_into(&mut dest).unwrap();
192        let raw = RawAttribute::from_bytes(&dest).unwrap();
193        let mapped2 = Icmp::try_from(&raw).unwrap();
194        assert_eq!(mapped2.get_type(), Icmp::TYPE);
195        assert_eq!(mapped2.icmp_type(), 2);
196        assert_eq!(mapped2.code(), 4);
197        assert_eq!(mapped2.data(), 8);
198    }
199
200    #[test]
201    #[should_panic = "out of range"]
202    fn icmp_write_into_unchecked() {
203        let _log = crate::tests::test_init_log();
204        let mapped = Icmp::new(2, 4, 8);
205        let raw: RawAttribute = mapped.to_raw();
206        let mut dest = vec![0; raw.padded_len() - 1];
207        mapped.write_into_unchecked(&mut dest);
208    }
209}