stun_rs/attributes/discovery/
change_request.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use crate::attributes::{
    stunt_attribute, AsVerifiable, AttributeDecoderContext, AttributeEncoderContext,
    DecodeAttributeValue, EncodeAttributeValue,
};
use enumflags2::{bitflags, BitFlags};

const CHANGE_REQUEST: u16 = 0x0003;

/// The change request attribute contains two flags to control the IP
/// address and port that the server uses to send the response. These
/// flags are called the "change IP" and "change port" flags.
#[bitflags]
#[repr(u32)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ChangeRequestFlags {
    /// Change port
    ChangePort = 1 << 1,
    /// Change IP
    ChangeIp = 1 << 2,
}

/// The change request attribute contains two flags to control the IP
/// address and port that the server uses to send the response. These
/// flags are called the "change IP" and "change port" flags.
/// This attribute is allowed only in the Binding Request. The
/// "change IP" and "change port" flags are useful for determining the
/// current filtering behavior of a NAT. They instruct the server to
/// send the Binding Responses from the alternate source IP address
/// and/or alternate port. The change request attribute is optional in
/// the Binding Request.
///
/// # Example
///```rust
/// # use stun_rs::attributes::discovery::ChangeRequest;
/// # use stun_rs::attributes::discovery::ChangeRequestFlags::*;
///
/// let change_request = ChangeRequest::new(Some(ChangeIp | ChangePort));
/// assert!(change_request.flags().contains(ChangeIp));
/// assert!(change_request.flags().contains(ChangePort));
///````
#[derive(Debug, Clone, Copy)]
pub struct ChangeRequest(u32);

impl ChangeRequest {
    /// creates a new change request attribute
    /// # Arguments
    /// - `change_ip`: The change IP flag
    /// - `change_port`: The change port flag
    /// # Returns
    /// The change request attribute
    pub fn new(flags: Option<BitFlags<ChangeRequestFlags>>) -> Self {
        let flags = match flags {
            Some(flags) => flags.bits(),
            None => 0,
        };
        ChangeRequest(flags)
    }

    /// Returns the flags set in the change request attribute
    pub fn flags(&self) -> BitFlags<ChangeRequestFlags> {
        BitFlags::<ChangeRequestFlags>::from_bits_truncate(self.0)
    }
}

impl DecodeAttributeValue for ChangeRequest {
    fn decode(ctx: AttributeDecoderContext) -> Result<(Self, usize), crate::StunError> {
        use crate::Decode;
        let (value, size) = u32::decode(ctx.raw_value())?;
        Ok((ChangeRequest(value), size))
    }
}

impl EncodeAttributeValue for ChangeRequest {
    fn encode(&self, mut ctx: AttributeEncoderContext) -> Result<usize, crate::StunError> {
        use crate::Encode;
        self.0.encode(ctx.raw_value_mut())
    }
}

impl AsVerifiable for ChangeRequest {}

stunt_attribute!(ChangeRequest, CHANGE_REQUEST);

#[cfg(test)]
mod tests {
    use super::*;
    use crate::attributes::discovery::change_request::ChangeRequestFlags::*;
    use crate::error::StunErrorType;
    use crate::StunAttribute;

    #[test]
    fn change_request_attribute() {
        let attr = ChangeRequest::new(None);
        assert!(!attr.flags().contains(ChangePort));
        assert!(!attr.flags().contains(ChangeIp));

        let attr = ChangeRequest::new(Some(ChangeIp | ChangePort));
        assert!(attr.flags().contains(ChangePort));
        assert!(attr.flags().contains(ChangeIp));
    }

    #[test]
    fn decode_change_request_attribute() {
        let dummy_msg = [];
        let raw_value = [0x00, 0x00, 0x04];
        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &raw_value);
        assert_eq!(
            ChangeRequest::decode(ctx).expect_err("Error expected"),
            StunErrorType::SmallBuffer
        );

        let raw_value = [0x00, 0x00, 0x00, 0x04];
        let ctx = AttributeDecoderContext::new(None, &dummy_msg, &raw_value);
        let (attr, size) = ChangeRequest::decode(ctx).unwrap();
        assert!(!attr.flags().contains(ChangePort));
        assert!(attr.flags().contains(ChangeIp));
        assert_eq!(size, 4);
    }

    #[test]
    fn encode_change_request_attribute() {
        let dummy_msg = [];
        let attr = ChangeRequest::new(Some(ChangePort.into()));
        let mut raw_value = [0x00; 3];
        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut raw_value);
        let result = attr.encode(ctx);
        assert_eq!(
            result.expect_err("Error expected"),
            StunErrorType::SmallBuffer
        );

        let mut raw_value = [0x00; 4];
        let ctx = AttributeEncoderContext::new(None, &dummy_msg, &mut raw_value);
        let size = attr.encode(ctx).unwrap();
        assert_eq!(raw_value, [0x00, 0x00, 0x00, 0x02]);
        assert_eq!(size, 4);
    }

    #[test]
    fn change_request_stunt_attribute() {
        let attr = StunAttribute::ChangeRequest(ChangeRequest::new(Some(ChangePort.into())));
        assert!(attr.is_change_request());
        assert!(attr.as_change_request().is_ok());
        assert!(attr.as_error_code().is_err());

        assert!(attr.attribute_type().is_comprehension_required());
        assert!(!attr.attribute_type().is_comprehension_optional());

        let dbg_fmt = format!("{:?}", attr);
        assert_eq!("ChangeRequest(ChangeRequest(2))", dbg_fmt);
    }
}