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