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
use std::io::Cursor;

use crate::definitions::{STUN_FINGERPRINT_ATTR_SIZE, STUN_INTEGRITY_ATTR_SIZE};
use crate::StunHeader;
use crate::{AttributeDecodeError, StunAttribute};

pub use super::errors::{IntegrityKeyGenerationError, MessageDecodeError, MessageEncodeError};
use super::message::StunMessage;

impl StunMessage {
    /// Decodes and returns the STUN message
    ///
    /// Arguments:
    ///
    /// * `bytes`: binary encoded message to decode from
    /// * `integrity_password`: Optionally set key that will be used for message integrity verification
    pub fn decode(
        bytes: &[u8],
        integrity_password: Option<&str>,
    ) -> Result<Self, MessageDecodeError> {
        let mut cursor = Cursor::new(bytes);

        // Decode header
        let header = StunHeader::decode(&mut cursor)?;
        // Decode attributes
        let mut attributes = Vec::new();

        let mut integrity_attr_passed = false;

        // Track for username/realm occurrences
        let mut username = None;
        let mut realm = None;

        // Message length does not include the 20-byte header.
        while cursor.position() < 20 + header.message_len as u64 {
            let decoded = StunAttribute::decode(&mut cursor, header.transaction_id);

            match decoded {
                Ok(decoded) => {
                    // Ignore all attributes after the MESSAGE-INTEGRITY attribute.
                    // As per [RFC5389 Section 15.4](https://tools.ietf.org/html/rfc5389#section-15.4)
                    if !integrity_attr_passed {
                        attributes.push(decoded.clone());
                    }

                    // Handle Fingerprint and MessageIntegrity attributes
                    match decoded {
                        StunAttribute::Username { value } => {
                            username = Some(value);
                        }
                        StunAttribute::Realm { value } => {
                            realm = Some(value);
                        }
                        StunAttribute::Fingerprint { value } => {
                            let attr_pos = cursor.position() as usize - STUN_FINGERPRINT_ATTR_SIZE;

                            // Make sure the Fingerprint attribute is the last one
                            if cursor.position() != bytes.len() as u64 {
                                return Err(
                                    MessageDecodeError::IncorrectFingerprintAttributePosition {
                                        msg_len: bytes.len(),
                                        attr_pos: attr_pos as usize,
                                    },
                                );
                            }

                            // Compute fingerprint for verification
                            let computed_fingerprint =
                                Self::calculate_fingerprint(&cursor.get_ref()[0..attr_pos]);

                            // Make sure the fingerprint matches
                            if computed_fingerprint != value {
                                return Err(MessageDecodeError::FingerprintMismatch {
                                    attr_value: value,
                                    computed_value: computed_fingerprint,
                                });
                            }

                            if integrity_attr_passed {
                                // Push the attribute to the list explicitly since it's after the MessageIntegrity attribute
                                attributes.push(decoded);
                            }
                        }
                        StunAttribute::MessageIntegrity { key } => {
                            // Mark MessageIntegrity attribute as passed so we can ignore attributes that happen after it
                            // With the exception of the Fingerprint attribute
                            integrity_attr_passed = true;

                            // If an `integrity_password` has been supplied, recalculate and verify the HMAC value
                            if let Some(integrity_password) = integrity_password {
                                let integrity_key = Self::calculate_integrity_key(
                                    integrity_password,
                                    realm.clone(),
                                    username.clone(),
                                )?;

                                let hmac = Self::calculate_integrity_hash(
                                    &integrity_key,
                                    &cursor.get_ref()[0..(cursor.position() as usize
                                        - STUN_INTEGRITY_ATTR_SIZE)
                                        as usize],
                                );

                                // Verify message integrity
                                if hmac != key {
                                    return Err(MessageDecodeError::MessageIntegrityFail {
                                        attr_value: key,
                                        computed_value: hmac,
                                    });
                                }
                            }
                        }
                        _ => {}
                    };
                }
                Err(err) => {
                    match err {
                        AttributeDecodeError::UnrecognizedAttributeType { attr_type } => {
                            // Attributes with type values between 0x8000 and 0xFFFF are
                            // comprehension-optional attributes, which means that those attributes
                            // can be ignored by the STUN agent if it does not understand them.
                            // Only return an error when the attribute is comprehension-required
                            if attr_type <= 0x8000 {
                                return Err(MessageDecodeError::AttributeDecodeFailure{source: err, transaction_id: header.transaction_id});
                            }
                        }
                        // Return an error on any other attribute decoding error
                        _ => return Err(MessageDecodeError::AttributeDecodeFailure{source: err, transaction_id: header.transaction_id}),
                    }
                }
            }
        }

        Ok(Self { header, attributes })
    }
}