Skip to main content

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