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