stun_rs/attributes/discovery/
change_request.rs

1use crate::attributes::{
2    stunt_attribute, AsVerifiable, AttributeDecoderContext, AttributeEncoderContext,
3    DecodeAttributeValue, EncodeAttributeValue,
4};
5use enumflags2::{bitflags, BitFlags};
6
7const CHANGE_REQUEST: u16 = 0x0003;
8
9/// The change request attribute contains two flags to control the IP
10/// address and port that the server uses to send the response. These
11/// flags are called the "change IP" and "change port" flags.
12#[bitflags]
13#[repr(u32)]
14#[derive(Copy, Clone, Debug, Eq, PartialEq)]
15pub enum ChangeRequestFlags {
16    /// Change port
17    ChangePort = 1 << 1,
18    /// Change IP
19    ChangeIp = 1 << 2,
20}
21
22/// The change request attribute contains two flags to control the IP
23/// address and port that the server uses to send the response. These
24/// flags are called the "change IP" and "change port" flags.
25/// This attribute is allowed only in the Binding Request. The
26/// "change IP" and "change port" flags are useful for determining the
27/// current filtering behavior of a NAT. They instruct the server to
28/// send the Binding Responses from the alternate source IP address
29/// and/or alternate port. The change request attribute is optional in
30/// the Binding Request.
31///
32/// # Example
33///```rust
34/// # use stun_rs::attributes::discovery::ChangeRequest;
35/// # use stun_rs::attributes::discovery::ChangeRequestFlags::*;
36///
37/// let change_request = ChangeRequest::new(Some(ChangeIp | ChangePort));
38/// assert!(change_request.flags().contains(ChangeIp));
39/// assert!(change_request.flags().contains(ChangePort));
40///````
41#[derive(Debug, Clone, Copy)]
42pub struct ChangeRequest(u32);
43
44impl ChangeRequest {
45    /// creates a new change request attribute
46    /// # Arguments
47    /// - `change_ip`: The change IP flag
48    /// - `change_port`: The change port flag
49    /// # Returns
50    /// The change request attribute
51    pub fn new(flags: Option<BitFlags<ChangeRequestFlags>>) -> Self {
52        let flags = match flags {
53            Some(flags) => flags.bits(),
54            None => 0,
55        };
56        ChangeRequest(flags)
57    }
58
59    /// Returns the flags set in the change request attribute
60    pub fn flags(&self) -> BitFlags<ChangeRequestFlags> {
61        BitFlags::<ChangeRequestFlags>::from_bits_truncate(self.0)
62    }
63}
64
65impl DecodeAttributeValue for ChangeRequest {
66    fn decode(ctx: AttributeDecoderContext) -> Result<(Self, usize), crate::StunError> {
67        use crate::Decode;
68        let (value, size) = u32::decode(ctx.raw_value())?;
69        Ok((ChangeRequest(value), size))
70    }
71}
72
73impl EncodeAttributeValue for ChangeRequest {
74    fn encode(&self, mut ctx: AttributeEncoderContext) -> Result<usize, crate::StunError> {
75        use crate::Encode;
76        self.0.encode(ctx.raw_value_mut())
77    }
78}
79
80impl AsVerifiable for ChangeRequest {}
81
82stunt_attribute!(ChangeRequest, CHANGE_REQUEST);
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use crate::attributes::discovery::change_request::ChangeRequestFlags::*;
88    use crate::error::StunErrorType;
89    use crate::StunAttribute;
90
91    #[test]
92    fn change_request_attribute() {
93        let attr = ChangeRequest::new(None);
94        assert!(!attr.flags().contains(ChangePort));
95        assert!(!attr.flags().contains(ChangeIp));
96
97        let attr = ChangeRequest::new(Some(ChangeIp | ChangePort));
98        assert!(attr.flags().contains(ChangePort));
99        assert!(attr.flags().contains(ChangeIp));
100    }
101
102    #[test]
103    fn decode_change_request_attribute() {
104        let dummy_msg = [];
105        let raw_value = [0x00, 0x00, 0x04];
106        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &raw_value);
107        assert_eq!(
108            ChangeRequest::decode(ctx).expect_err("Error expected"),
109            StunErrorType::SmallBuffer
110        );
111
112        let raw_value = [0x00, 0x00, 0x00, 0x04];
113        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &raw_value);
114        let (attr, size) = ChangeRequest::decode(ctx).unwrap();
115        assert!(!attr.flags().contains(ChangePort));
116        assert!(attr.flags().contains(ChangeIp));
117        assert_eq!(size, 4);
118    }
119
120    #[test]
121    fn encode_change_request_attribute() {
122        let dummy_msg = [];
123        let attr = ChangeRequest::new(Some(ChangePort.into()));
124        let mut raw_value = [0x00; 3];
125        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut raw_value);
126        let result = attr.encode(ctx);
127        assert_eq!(
128            result.expect_err("Error expected"),
129            StunErrorType::SmallBuffer
130        );
131
132        let mut raw_value = [0x00; 4];
133        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut raw_value);
134        let size = attr.encode(ctx).unwrap();
135        assert_eq!(raw_value, [0x00, 0x00, 0x00, 0x02]);
136        assert_eq!(size, 4);
137    }
138
139    #[test]
140    fn change_request_stunt_attribute() {
141        let attr = StunAttribute::ChangeRequest(ChangeRequest::new(Some(ChangePort.into())));
142        assert!(attr.is_change_request());
143        assert!(attr.as_change_request().is_ok());
144        assert!(attr.as_error_code().is_err());
145
146        assert!(attr.attribute_type().is_comprehension_required());
147        assert!(!attr.attribute_type().is_comprehension_optional());
148
149        let dbg_fmt = format!("{:?}", attr);
150        assert_eq!("ChangeRequest(ChangeRequest(2))", dbg_fmt);
151    }
152}