faster_stun/attribute/
error.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
use crate::{util, StunError};

use bytes::{BufMut, BytesMut};
use num_enum::TryFromPrimitive;

use std::cmp::{Eq, PartialEq};
use std::convert::{Into, TryFrom};

/// The following error codes, along with their recommended reason
/// phrases, are defined:
///
/// 300  Try Alternate: The client should contact an alternate server for
///      this request.  This error response MUST only be sent if the
///      request included either a USERNAME or USERHASH attribute and a
///      valid MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 attribute;
///      otherwise, it MUST NOT be sent and error code 400 (Bad Request)
///      is suggested.  This error response MUST be protected with the
///      MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 attribute, and
///      receivers MUST validate the MESSAGE-INTEGRITY or MESSAGE-
///      INTEGRITY-SHA256 of this response before redirecting themselves
///      to an alternate server.
///      Note: Failure to generate and validate message integrity for a
///      300 response allows an on-path attacker to falsify a 300
///      response thus causing subsequent STUN messages to be sent to a
///      victim.
///      
/// 400  Bad Request: The request was malformed.  The client SHOULD NOT
///      retry the request without modification from the previous
///      attempt.  The server may not be able to generate a valid
///      MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 for this error, so
///      the client MUST NOT expect a valid MESSAGE-INTEGRITY or MESSAGE-
///      INTEGRITY-SHA256 attribute on this response.
///      
/// 401  Unauthenticated: The request did not contain the correct
///      credentials to proceed.  The client should retry the request
///      with proper credentials.
///      
/// 420  Unknown Attribute: The server received a STUN packet containing
///      a comprehension-required attribute that it did not understand.
///      The server MUST put this unknown attribute in the UNKNOWN-
///      ATTRIBUTE attribute of its error response.
///      
/// 438  Stale Nonce: The NONCE used by the client was no longer valid.
///      The client should retry, using the NONCE provided in the
///      response.
///      
/// 500  Server Error: The server has suffered a temporary error.  The
///      client should try again.
#[repr(u16)]
#[derive(TryFromPrimitive, PartialEq, Eq, Copy, Clone, Debug)]
pub enum Kind {
    TryAlternate = 0x0300,
    BadRequest = 0x0400,
    Unauthorized = 0x0401,
    Forbidden = 0x0403,
    RequestTimedout = 0x0408,
    UnknownAttribute = 0x0414,
    AllocationMismatch = 0x0425,
    StaleNonce = 0x0426,
    AddressFamilyNotSupported = 0x0428,
    WrongCredentials = 0x0429,
    UnsupportedTransportAddress = 0x042A,
    AllocationQuotaReached = 0x0456,
    ServerError = 0x0500,
    InsufficientCapacity = 0x0508,
}

/// [RFC3629]: https://datatracker.ietf.org/doc/html/rfc3629
/// [RFC7231]: https://datatracker.ietf.org/doc/html/rfc7231
/// [RFC3261]: https://datatracker.ietf.org/doc/html/rfc3261
/// [RFC3629]: https://datatracker.ietf.org/doc/html/rfc3629
///
/// The ERROR-CODE attribute is used in error response messages.  It
/// contains a numeric error code value in the range of 300 to 699 plus a
/// textual reason phrase encoded in UTF-8 [RFC3629]; it is also
/// consistent in its code assignments and semantics with SIP [RFC3261]
/// and HTTP [RFC7231].  The reason phrase is meant for diagnostic
/// purposes and can be anything appropriate for the error code.
/// Recommended reason phrases for the defined error codes are included
/// in the IANA registry for error codes.  The reason phrase MUST be a
/// UTF-8-encoded [RFC3629] sequence of fewer than 128 characters (which
/// can be as long as 509 bytes when encoding them or 763 bytes when
/// decoding them).
///
/// ```text
/// 0                   1                   2                   3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |           Reserved, should be 0         |Class|     Number    |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |      Reason Phrase (variable)                                ..
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
///              Figure 7: Format of ERROR-CODE Attribute
/// ```
///
/// To facilitate processing, the class of the error code (the hundreds
/// digit) is encoded separately from the rest of the code, as shown in
/// Figure 7.
///
/// The Reserved bits SHOULD be 0 and are for alignment on 32-bit
/// boundaries.  Receivers MUST ignore these bits.  The Class represents
/// the hundreds digit of the error code.  The value MUST be between 3
/// and 6.  The Number represents the binary encoding of the error code
/// modulo 100, and its value MUST be between 0 and 99.
#[derive(Clone, Debug)]
pub struct Error<'a> {
    pub code: u16,
    pub message: &'a str,
}

impl Error<'_> {
    /// create error from error type.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use faster_stun::attribute::*;
    ///
    /// Error::from(ErrKind::TryAlternate);
    /// ```
    pub fn from(code: Kind) -> Self {
        Self {
            code: code as u16,
            message: code.into(),
        }
    }

    /// encode the error type as bytes.
    ///
    /// # Unit Test
    ///
    /// ```
    /// use bytes::BytesMut;
    /// use faster_stun::attribute::*;
    ///
    /// let buffer = [
    ///     0x00u8, 0x00, 0x03, 0x00, 0x54, 0x72, 0x79, 0x20, 0x41, 0x6c, 0x74,
    ///     0x65, 0x72, 0x6e, 0x61, 0x74, 0x65,
    /// ];
    ///
    /// let mut buf = BytesMut::with_capacity(1280);
    /// let error = Error::from(ErrKind::TryAlternate);
    /// error.into(&mut buf);
    /// assert_eq!(&buf[..], &buffer);
    /// ```
    pub fn into(self, buf: &mut BytesMut) {
        buf.put_u16(0x0000);
        buf.put_u16(self.code);
        buf.put(self.message.as_bytes());
    }
}

impl<'a> TryFrom<&'a [u8]> for Error<'a> {
    type Error = StunError;

    /// # Unit Test
    ///
    /// ```
    /// use faster_stun::attribute::*;
    /// use std::convert::TryFrom;
    ///
    /// let buffer = [
    ///     0x00u8, 0x00, 0x03, 0x00, 0x54, 0x72, 0x79, 0x20, 0x41, 0x6c, 0x74,
    ///     0x65, 0x72, 0x6e, 0x61, 0x74, 0x65,
    /// ];
    ///
    /// let error = Error::try_from(&buffer[..]).unwrap();
    /// assert_eq!(error.code, ErrKind::TryAlternate as u16);
    /// assert_eq!(error.message, "Try Alternate");
    /// ```
    fn try_from(packet: &'a [u8]) -> Result<Self, Self::Error> {
        if !(packet.len() >= 4) {
            return Err(StunError::InvalidInput);
        }

        if !(util::as_u16(&packet[..2]) == 0x0000) {
            return Err(StunError::InvalidInput);
        }

        Ok(Self {
            code: util::as_u16(&packet[2..4]),
            message: std::str::from_utf8(&packet[4..]).map_err(|_| StunError::FatalError)?,
        })
    }
}

impl From<Kind> for &'static str {
    /// # Unit Test
    ///
    /// ```
    /// use faster_stun::attribute::*;
    /// use std::convert::Into;
    ///
    /// let err: &'static str = ErrKind::TryAlternate.into();
    /// assert_eq!(err, "Try Alternate");
    /// ```
    #[rustfmt::skip]
    fn from(val: Kind) -> Self {
        match val {
            Kind::TryAlternate => "Try Alternate",
            Kind::BadRequest => "Bad Request",
            Kind::Unauthorized => "Unauthorized",
            Kind::Forbidden => "Forbidden",
            Kind::RequestTimedout => "Request Timed out",
            Kind::UnknownAttribute => "Unknown Attribute",
            Kind::AllocationMismatch => "Allocation Mismatch",
            Kind::StaleNonce => "Stale Nonce",
            Kind::AddressFamilyNotSupported => "Address Family not Supported",
            Kind::WrongCredentials => "Wrong Credentials",
            Kind::UnsupportedTransportAddress => "Unsupported Transport Address",
            Kind::AllocationQuotaReached => "Allocation Quota Reached",
            Kind::ServerError => "Server Error",
            Kind::InsufficientCapacity => "Insufficient Capacity",
        }
    }
}

impl Eq for Error<'_> {}
impl PartialEq for Error<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.code == other.code
    }
}