turn_types/attribute/
lifetime.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 lifetime [`Attribute`]
14#[derive(Debug, Clone)]
15pub struct Lifetime {
16    seconds: u32,
17}
18
19impl AttributeStaticType for Lifetime {
20    const TYPE: AttributeType = AttributeType::new(0x000D);
21}
22
23impl Attribute for Lifetime {
24    fn get_type(&self) -> AttributeType {
25        Self::TYPE
26    }
27
28    fn length(&self) -> u16 {
29        4
30    }
31}
32
33impl AttributeWrite for Lifetime {
34    fn to_raw(&self) -> RawAttribute {
35        let mut buf = [0; 4];
36        BigEndian::write_u32(&mut buf[..4], self.seconds);
37        RawAttribute::new(self.get_type(), &buf).into_owned()
38    }
39
40    fn write_into_unchecked(&self, dest: &mut [u8]) {
41        self.write_header_unchecked(dest);
42        BigEndian::write_u32(&mut dest[4..8], self.seconds);
43    }
44}
45
46impl AttributeFromRaw<'_> for Lifetime {
47    fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
48    where
49        Self: Sized,
50    {
51        Self::try_from(raw)
52    }
53}
54
55impl TryFrom<&RawAttribute<'_>> for Lifetime {
56    type Error = StunParseError;
57    fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
58        raw.check_type_and_len(Self::TYPE, 4..=4)?;
59        Ok(Self {
60            seconds: BigEndian::read_u32(&raw.value),
61        })
62    }
63}
64
65impl Lifetime {
66    /// Create a new [`Lifetime`] [`Attribute`]
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// # use turn_types::attribute::*;
72    /// let lifetime = Lifetime::new (42);
73    /// assert_eq!(lifetime.seconds(), 42);
74    /// ```
75    pub fn new(seconds: u32) -> Self {
76        Self { seconds }
77    }
78
79    /// The number of seconds stored in a [`Lifetime`] [`Attribute`]
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// # use turn_types::attribute::*;
85    /// let lifetime = Lifetime::new (42);
86    /// assert_eq!(lifetime.seconds(), 42);
87    /// ```
88    pub fn seconds(&self) -> u32 {
89        self.seconds
90    }
91}
92
93impl std::fmt::Display for Lifetime {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{}: '{}'", self.get_type(), self.seconds)
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use byteorder::{BigEndian, ByteOrder};
103
104    #[test]
105    fn lifetime() {
106        let _log = crate::tests::test_init_log();
107        let lifetime = Lifetime::new(600);
108        assert_eq!(lifetime.get_type(), Lifetime::TYPE);
109        assert_eq!(lifetime.seconds(), 600);
110        let raw: RawAttribute = lifetime.to_raw();
111        println!("{}", raw);
112        assert_eq!(raw.get_type(), Lifetime::TYPE);
113        let lifetime2 = Lifetime::try_from(&raw).unwrap();
114        assert_eq!(lifetime2.get_type(), Lifetime::TYPE);
115        assert_eq!(lifetime2.seconds(), 600);
116        // provide incorrectly typed data
117        let mut data: Vec<_> = raw.into();
118        BigEndian::write_u16(&mut data[0..2], 0);
119        assert!(matches!(
120            Lifetime::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
121            Err(StunParseError::WrongAttributeImplementation)
122        ));
123    }
124}