faster_stun/attribute/
error.rs

1use crate::{util, StunError};
2
3use bytes::{BufMut, BytesMut};
4use num_enum::TryFromPrimitive;
5
6use std::cmp::{Eq, PartialEq};
7use std::convert::{Into, TryFrom};
8
9/// The following error codes, along with their recommended reason
10/// phrases, are defined:
11///
12/// 300  Try Alternate: The client should contact an alternate server for
13///      this request.  This error response MUST only be sent if the
14///      request included either a USERNAME or USERHASH attribute and a
15///      valid MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 attribute;
16///      otherwise, it MUST NOT be sent and error code 400 (Bad Request)
17///      is suggested.  This error response MUST be protected with the
18///      MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 attribute, and
19///      receivers MUST validate the MESSAGE-INTEGRITY or MESSAGE-
20///      INTEGRITY-SHA256 of this response before redirecting themselves
21///      to an alternate server.
22///      Note: Failure to generate and validate message integrity for a
23///      300 response allows an on-path attacker to falsify a 300
24///      response thus causing subsequent STUN messages to be sent to a
25///      victim.
26///      
27/// 400  Bad Request: The request was malformed.  The client SHOULD NOT
28///      retry the request without modification from the previous
29///      attempt.  The server may not be able to generate a valid
30///      MESSAGE-INTEGRITY or MESSAGE-INTEGRITY-SHA256 for this error, so
31///      the client MUST NOT expect a valid MESSAGE-INTEGRITY or MESSAGE-
32///      INTEGRITY-SHA256 attribute on this response.
33///      
34/// 401  Unauthenticated: The request did not contain the correct
35///      credentials to proceed.  The client should retry the request
36///      with proper credentials.
37///      
38/// 420  Unknown Attribute: The server received a STUN packet containing
39///      a comprehension-required attribute that it did not understand.
40///      The server MUST put this unknown attribute in the UNKNOWN-
41///      ATTRIBUTE attribute of its error response.
42///      
43/// 438  Stale Nonce: The NONCE used by the client was no longer valid.
44///      The client should retry, using the NONCE provided in the
45///      response.
46///      
47/// 500  Server Error: The server has suffered a temporary error.  The
48///      client should try again.
49#[repr(u16)]
50#[derive(TryFromPrimitive, PartialEq, Eq, Copy, Clone, Debug)]
51pub enum Kind {
52    TryAlternate = 0x0300,
53    BadRequest = 0x0400,
54    Unauthorized = 0x0401,
55    Forbidden = 0x0403,
56    RequestTimedout = 0x0408,
57    UnknownAttribute = 0x0414,
58    AllocationMismatch = 0x0425,
59    StaleNonce = 0x0426,
60    AddressFamilyNotSupported = 0x0428,
61    WrongCredentials = 0x0429,
62    UnsupportedTransportAddress = 0x042A,
63    AllocationQuotaReached = 0x0456,
64    ServerError = 0x0500,
65    InsufficientCapacity = 0x0508,
66}
67
68/// [RFC3629]: https://datatracker.ietf.org/doc/html/rfc3629
69/// [RFC7231]: https://datatracker.ietf.org/doc/html/rfc7231
70/// [RFC3261]: https://datatracker.ietf.org/doc/html/rfc3261
71/// [RFC3629]: https://datatracker.ietf.org/doc/html/rfc3629
72///
73/// The ERROR-CODE attribute is used in error response messages.  It
74/// contains a numeric error code value in the range of 300 to 699 plus a
75/// textual reason phrase encoded in UTF-8 [RFC3629]; it is also
76/// consistent in its code assignments and semantics with SIP [RFC3261]
77/// and HTTP [RFC7231].  The reason phrase is meant for diagnostic
78/// purposes and can be anything appropriate for the error code.
79/// Recommended reason phrases for the defined error codes are included
80/// in the IANA registry for error codes.  The reason phrase MUST be a
81/// UTF-8-encoded [RFC3629] sequence of fewer than 128 characters (which
82/// can be as long as 509 bytes when encoding them or 763 bytes when
83/// decoding them).
84///
85/// ```text
86/// 0                   1                   2                   3
87/// 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
88/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89/// |           Reserved, should be 0         |Class|     Number    |
90/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
91/// |      Reason Phrase (variable)                                ..
92/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93///
94///              Figure 7: Format of ERROR-CODE Attribute
95/// ```
96///
97/// To facilitate processing, the class of the error code (the hundreds
98/// digit) is encoded separately from the rest of the code, as shown in
99/// Figure 7.
100///
101/// The Reserved bits SHOULD be 0 and are for alignment on 32-bit
102/// boundaries.  Receivers MUST ignore these bits.  The Class represents
103/// the hundreds digit of the error code.  The value MUST be between 3
104/// and 6.  The Number represents the binary encoding of the error code
105/// modulo 100, and its value MUST be between 0 and 99.
106#[derive(Clone, Debug)]
107pub struct Error<'a> {
108    pub code: u16,
109    pub message: &'a str,
110}
111
112impl Error<'_> {
113    /// create error from error type.
114    ///
115    /// # Example
116    ///
117    /// ```no_run
118    /// use faster_stun::attribute::*;
119    ///
120    /// Error::from(ErrKind::TryAlternate);
121    /// ```
122    pub fn from(code: Kind) -> Self {
123        Self {
124            code: code as u16,
125            message: code.into(),
126        }
127    }
128
129    /// encode the error type as bytes.
130    ///
131    /// # Unit Test
132    ///
133    /// ```
134    /// use bytes::BytesMut;
135    /// use faster_stun::attribute::*;
136    ///
137    /// let buffer = [
138    ///     0x00u8, 0x00, 0x03, 0x00, 0x54, 0x72, 0x79, 0x20, 0x41, 0x6c, 0x74,
139    ///     0x65, 0x72, 0x6e, 0x61, 0x74, 0x65,
140    /// ];
141    ///
142    /// let mut buf = BytesMut::with_capacity(1280);
143    /// let error = Error::from(ErrKind::TryAlternate);
144    /// error.into(&mut buf);
145    /// assert_eq!(&buf[..], &buffer);
146    /// ```
147    pub fn into(self, buf: &mut BytesMut) {
148        buf.put_u16(0x0000);
149        buf.put_u16(self.code);
150        buf.put(self.message.as_bytes());
151    }
152}
153
154impl<'a> TryFrom<&'a [u8]> for Error<'a> {
155    type Error = StunError;
156
157    /// # Unit Test
158    ///
159    /// ```
160    /// use faster_stun::attribute::*;
161    /// use std::convert::TryFrom;
162    ///
163    /// let buffer = [
164    ///     0x00u8, 0x00, 0x03, 0x00, 0x54, 0x72, 0x79, 0x20, 0x41, 0x6c, 0x74,
165    ///     0x65, 0x72, 0x6e, 0x61, 0x74, 0x65,
166    /// ];
167    ///
168    /// let error = Error::try_from(&buffer[..]).unwrap();
169    /// assert_eq!(error.code, ErrKind::TryAlternate as u16);
170    /// assert_eq!(error.message, "Try Alternate");
171    /// ```
172    fn try_from(packet: &'a [u8]) -> Result<Self, Self::Error> {
173        if !(packet.len() >= 4) {
174            return Err(StunError::InvalidInput);
175        }
176
177        if !(util::as_u16(&packet[..2]) == 0x0000) {
178            return Err(StunError::InvalidInput);
179        }
180
181        Ok(Self {
182            code: util::as_u16(&packet[2..4]),
183            message: std::str::from_utf8(&packet[4..]).map_err(|_| StunError::FatalError)?,
184        })
185    }
186}
187
188impl From<Kind> for &'static str {
189    /// # Unit Test
190    ///
191    /// ```
192    /// use faster_stun::attribute::*;
193    /// use std::convert::Into;
194    ///
195    /// let err: &'static str = ErrKind::TryAlternate.into();
196    /// assert_eq!(err, "Try Alternate");
197    /// ```
198    #[rustfmt::skip]
199    fn from(val: Kind) -> Self {
200        match val {
201            Kind::TryAlternate => "Try Alternate",
202            Kind::BadRequest => "Bad Request",
203            Kind::Unauthorized => "Unauthorized",
204            Kind::Forbidden => "Forbidden",
205            Kind::RequestTimedout => "Request Timed out",
206            Kind::UnknownAttribute => "Unknown Attribute",
207            Kind::AllocationMismatch => "Allocation Mismatch",
208            Kind::StaleNonce => "Stale Nonce",
209            Kind::AddressFamilyNotSupported => "Address Family not Supported",
210            Kind::WrongCredentials => "Wrong Credentials",
211            Kind::UnsupportedTransportAddress => "Unsupported Transport Address",
212            Kind::AllocationQuotaReached => "Allocation Quota Reached",
213            Kind::ServerError => "Server Error",
214            Kind::InsufficientCapacity => "Insufficient Capacity",
215        }
216    }
217}
218
219impl Eq for Error<'_> {}
220impl PartialEq for Error<'_> {
221    fn eq(&self, other: &Self) -> bool {
222        self.code == other.code
223    }
224}