use alloc::borrow::ToOwned;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryFrom;
use byteorder::{BigEndian, ByteOrder};
use crate::message::{StunParseError, StunWriteError};
use super::{
Attribute, AttributeExt, AttributeFromRaw, AttributeStaticType, AttributeType, AttributeWrite,
AttributeWriteExt, RawAttribute,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErrorCode {
code: u16,
reason: String,
}
impl AttributeStaticType for ErrorCode {
const TYPE: AttributeType = AttributeType(0x0009);
}
impl Attribute for ErrorCode {
fn get_type(&self) -> AttributeType {
Self::TYPE
}
fn length(&self) -> u16 {
self.reason.len() as u16 + 4
}
}
impl AttributeWrite for ErrorCode {
fn to_raw(&self) -> RawAttribute<'_> {
let mut data = Vec::with_capacity(self.length() as usize);
data.push(0u8);
data.push(0u8);
data.push((self.code / 100) as u8);
data.push((self.code % 100) as u8);
data.extend(self.reason.as_bytes());
RawAttribute::new_owned(ErrorCode::TYPE, data.into_boxed_slice())
}
fn write_into_unchecked(&self, dest: &mut [u8]) {
let len = self.padded_len();
let mut offset = self.write_header_unchecked(dest);
offset += self.write_into_data(&mut dest[offset..]);
if len - offset > 0 {
dest[offset..len].fill(0);
}
}
}
impl AttributeFromRaw<'_> for ErrorCode {
fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
where
Self: Sized,
{
Self::try_from(raw)
}
}
impl TryFrom<&RawAttribute<'_>> for ErrorCode {
type Error = StunParseError;
fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
raw.check_type_and_len(Self::TYPE, 4..=763 + 4)?;
let code_h = (raw.value[2] & 0x7) as u16;
let code_tens = raw.value[3] as u16;
if !(3..7).contains(&code_h) || code_tens > 99 {
return Err(StunParseError::InvalidAttributeData);
}
let code = code_h * 100 + code_tens;
Ok(Self {
code,
reason: core::str::from_utf8(&raw.value[4..])
.map_err(|_| StunParseError::InvalidAttributeData)?
.to_owned(),
})
}
}
#[derive(Debug)]
pub struct ErrorCodeBuilder<'reason> {
code: u16,
reason: Option<&'reason str>,
}
impl<'reason> ErrorCodeBuilder<'reason> {
fn new(code: u16) -> Self {
Self { code, reason: None }
}
pub fn reason(mut self, reason: &'reason str) -> Self {
self.reason = Some(reason);
self
}
pub fn build(self) -> Result<ErrorCode, StunWriteError> {
if !(300..700).contains(&self.code) {
return Err(StunWriteError::OutOfRange {
value: self.code as usize,
min: 300,
max: 699,
});
}
let reason = self
.reason
.unwrap_or_else(|| ErrorCode::default_reason_for_code(self.code))
.to_owned();
Ok(ErrorCode {
code: self.code,
reason,
})
}
}
impl ErrorCode {
pub const TRY_ALTERNATE: u16 = 300;
pub const BAD_REQUEST: u16 = 400;
pub const UNAUTHORIZED: u16 = 401;
pub const FORBIDDEN: u16 = 403;
pub const UNKNOWN_ATTRIBUTE: u16 = 420;
pub const ALLOCATION_MISMATCH: u16 = 437;
pub const STALE_NONCE: u16 = 438;
pub const ADDRESS_FAMILY_NOT_SUPPORTED: u16 = 440;
pub const WRONG_CREDENTIALS: u16 = 441;
pub const UNSUPPORTED_TRANSPORT_PROTOCOL: u16 = 442;
pub const PEER_ADDRESS_FAMILY_MISMATCH: u16 = 443;
pub const CONNECTION_ALREADY_EXISTS: u16 = 446;
pub const CONNECTION_TIMEOUT_OR_FAILURE: u16 = 447;
pub const ALLOCATION_QUOTA_REACHED: u16 = 486;
pub const ROLE_CONFLICT: u16 = 487;
pub const SERVER_ERROR: u16 = 500;
pub const INSUFFICIENT_CAPACITY: u16 = 508;
pub fn builder<'reason>(code: u16) -> ErrorCodeBuilder<'reason> {
ErrorCodeBuilder::new(code)
}
pub fn new(code: u16, reason: &str) -> Result<Self, StunWriteError> {
if !(300..700).contains(&code) {
return Err(StunWriteError::OutOfRange {
value: code as usize,
min: 300,
max: 699,
});
}
Ok(Self {
code,
reason: reason.to_owned(),
})
}
pub fn code(&self) -> u16 {
self.code
}
pub fn reason(&self) -> &str {
&self.reason
}
pub fn default_reason_for_code(code: u16) -> &'static str {
match code {
Self::TRY_ALTERNATE => "Try Alternate",
Self::BAD_REQUEST => "Bad Request",
Self::UNAUTHORIZED => "Unauthorized",
Self::FORBIDDEN => "Forbidden",
Self::UNKNOWN_ATTRIBUTE => "Unknown Attribute",
Self::ALLOCATION_MISMATCH => "Allocation Mismatch",
Self::STALE_NONCE => "Stale Nonce",
Self::ADDRESS_FAMILY_NOT_SUPPORTED => "Address Family Not Supported",
Self::WRONG_CREDENTIALS => "Wrong Credentials",
Self::UNSUPPORTED_TRANSPORT_PROTOCOL => "Unsupported Transport Protocol",
Self::PEER_ADDRESS_FAMILY_MISMATCH => "Peer Address Family Mismatch",
Self::CONNECTION_ALREADY_EXISTS => "Connection Already Exists",
Self::CONNECTION_TIMEOUT_OR_FAILURE => "Connection Timeout or Failure",
Self::ALLOCATION_QUOTA_REACHED => "Allocation Quota Reached",
Self::ROLE_CONFLICT => "Role Conflict",
Self::SERVER_ERROR => "Server Error",
Self::INSUFFICIENT_CAPACITY => "Insufficient Capacity",
_ => "Unknown",
}
}
fn write_into_data(&self, dest: &mut [u8]) -> usize {
dest[0] = 0u8;
dest[1] = 0u8;
dest[2] = (self.code / 100) as u8;
dest[3] = (self.code % 100) as u8;
let bytes = self.reason.as_bytes();
dest[4..4 + bytes.len()].copy_from_slice(bytes);
4 + bytes.len()
}
}
impl core::fmt::Display for ErrorCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {} '{}'", Self::TYPE, self.code, self.reason)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnknownAttributes {
attributes: Vec<AttributeType>,
}
impl AttributeStaticType for UnknownAttributes {
const TYPE: AttributeType = AttributeType(0x000A);
}
impl Attribute for UnknownAttributes {
fn get_type(&self) -> AttributeType {
Self::TYPE
}
fn length(&self) -> u16 {
(self.attributes.len() as u16) * 2
}
}
impl AttributeWrite for UnknownAttributes {
fn to_raw(&self) -> RawAttribute<'_> {
let mut data = vec![0; self.length() as usize];
self.write_into_data(&mut data);
RawAttribute::new_owned(UnknownAttributes::TYPE, data.into_boxed_slice())
}
fn write_into_unchecked(&self, dest: &mut [u8]) {
let len = self.padded_len();
let mut offset = self.write_header_unchecked(dest);
offset += self.write_into_data(&mut dest[offset..]);
if len - offset > 0 {
dest[offset..len].fill(0);
}
}
}
impl AttributeFromRaw<'_> for UnknownAttributes {
fn from_raw_ref(raw: &RawAttribute) -> Result<Self, StunParseError>
where
Self: Sized,
{
Self::try_from(raw)
}
}
impl TryFrom<&RawAttribute<'_>> for UnknownAttributes {
type Error = StunParseError;
fn try_from(raw: &RawAttribute) -> Result<Self, Self::Error> {
if raw.header.atype != Self::TYPE {
return Err(StunParseError::WrongAttributeImplementation);
}
if raw.value.len() % 2 != 0 {
return Err(StunParseError::Truncated {
expected: raw.value.len() + 1,
actual: raw.value.len(),
});
}
let mut attrs = vec![];
for attr in raw.value.chunks_exact(2) {
attrs.push(BigEndian::read_u16(attr).into());
}
Ok(Self { attributes: attrs })
}
}
impl UnknownAttributes {
pub fn new(attrs: &[AttributeType]) -> Self {
Self {
attributes: attrs.to_vec(),
}
}
pub fn add_attribute(&mut self, attr: AttributeType) {
if !self.has_attribute(attr) {
self.attributes.push(attr);
}
}
pub fn has_attribute(&self, attr: AttributeType) -> bool {
self.attributes.contains(&attr)
}
fn write_into_data(&self, dest: &mut [u8]) -> usize {
let mut offset = 0;
for attr in &self.attributes {
BigEndian::write_u16(&mut dest[offset..offset + 2], (*attr).into());
offset += 2;
}
offset
}
}
impl core::fmt::Display for UnknownAttributes {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {:?}", Self::TYPE, self.attributes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attribute::{AlternateServer, Nonce, Realm};
use tracing::trace;
const CODES: [u16; 17] = [
300, 301, 400, 401, 403, 420, 437, 438, 440, 441, 442, 443, 486, 487, 500, 508, 699,
];
#[test]
fn error_code() {
let _log = crate::tests::test_init_log();
for code in CODES {
let reason = ErrorCode::default_reason_for_code(code);
let err = ErrorCode::new(code, reason).unwrap();
trace!("{err}");
assert_eq!(err.code(), code);
assert_eq!(err.reason(), reason);
}
}
#[test]
fn error_code_raw() {
let _log = crate::tests::test_init_log();
for code in CODES {
let reason = ErrorCode::default_reason_for_code(code);
let err = ErrorCode::new(code, reason).unwrap();
let raw = RawAttribute::from(&err);
trace!("{raw}");
assert_eq!(raw.get_type(), ErrorCode::TYPE);
let err2 = ErrorCode::try_from(&raw).unwrap();
assert_eq!(err2.code(), code);
assert_eq!(err2.reason(), reason);
}
}
#[test]
fn error_code_write_into() {
let _log = crate::tests::test_init_log();
for code in CODES {
let reason = ErrorCode::default_reason_for_code(code);
let err = ErrorCode::new(code, reason).unwrap();
let raw = RawAttribute::from(&err);
let mut dest = vec![0; raw.padded_len()];
err.write_into(&mut dest).unwrap();
let raw = RawAttribute::from_bytes(&dest).unwrap();
let err2 = ErrorCode::try_from(&raw).unwrap();
assert_eq!(err2.code(), code);
assert_eq!(err2.reason(), reason);
}
}
#[test]
#[should_panic(expected = "out of range")]
fn error_code_write_into_unchecked() {
let _log = crate::tests::test_init_log();
let reason = ErrorCode::default_reason_for_code(CODES[0]);
let err = ErrorCode::new(CODES[0], reason).unwrap();
let raw = RawAttribute::from(&err);
let mut dest = vec![0; raw.padded_len() - 1];
err.write_into_unchecked(&mut dest);
}
fn error_code_new(code: u16) -> ErrorCode {
let reason = ErrorCode::default_reason_for_code(code);
ErrorCode::new(code, reason).unwrap()
}
#[test]
fn error_code_parse_short() {
let _log = crate::tests::test_init_log();
let err = error_code_new(420);
let raw = RawAttribute::from(&err);
let mut data: Vec<_> = raw.into();
let len = 0;
BigEndian::write_u16(&mut data[2..4], len as u16);
assert!(matches!(
ErrorCode::try_from(&RawAttribute::from_bytes(data[..len + 4].as_ref()).unwrap()),
Err(StunParseError::Truncated {
expected: 4,
actual: 0
})
));
}
#[test]
fn error_code_parse_wrong_implementation() {
let _log = crate::tests::test_init_log();
let err = error_code_new(420);
let raw = RawAttribute::from(&err);
let mut data: Vec<_> = raw.into();
BigEndian::write_u16(&mut data[0..2], 0);
assert!(matches!(
ErrorCode::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
Err(StunParseError::WrongAttributeImplementation)
));
}
#[test]
fn error_code_parse_out_of_range_code() {
let _log = crate::tests::test_init_log();
let err = error_code_new(420);
let raw = RawAttribute::from(&err);
let mut data: Vec<_> = raw.into();
data[6] = 7;
assert!(matches!(
ErrorCode::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
Err(StunParseError::InvalidAttributeData)
));
}
#[test]
fn error_code_parse_invalid_reason() {
let _log = crate::tests::test_init_log();
let err = error_code_new(420);
let raw = RawAttribute::from(&err);
let mut data: Vec<_> = raw.into();
data[10] = 0x88;
assert!(matches!(
ErrorCode::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
Err(StunParseError::InvalidAttributeData)
));
}
#[test]
fn error_code_build_default_reason() {
let _log = crate::tests::test_init_log();
let err = ErrorCode::builder(420).build().unwrap();
assert_eq!(err.code(), 420);
assert!(!err.reason().is_empty());
}
#[test]
fn error_code_build_out_of_range() {
let _log = crate::tests::test_init_log();
assert!(matches!(
ErrorCode::builder(700).build(),
Err(StunWriteError::OutOfRange {
value: 700,
min: _,
max: _
})
));
}
#[test]
fn error_code_new_out_of_range() {
let _log = crate::tests::test_init_log();
assert!(matches!(
ErrorCode::new(700, "some-reason"),
Err(StunWriteError::OutOfRange {
value: 700,
min: _,
max: _
})
));
}
#[test]
fn unknown_attributes() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
unknown.add_attribute(AlternateServer::TYPE);
trace!("{unknown}");
assert!(unknown.has_attribute(Realm::TYPE));
assert!(unknown.has_attribute(AlternateServer::TYPE));
assert!(!unknown.has_attribute(Nonce::TYPE));
}
#[test]
fn unknown_attributes_raw() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
let raw = RawAttribute::from(&unknown);
assert_eq!(raw.get_type(), UnknownAttributes::TYPE);
let unknown2 = UnknownAttributes::try_from(&raw).unwrap();
assert!(unknown2.has_attribute(Realm::TYPE));
assert!(unknown2.has_attribute(AlternateServer::TYPE));
assert!(!unknown2.has_attribute(Nonce::TYPE));
}
#[test]
fn unknown_attributes_raw_short() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
let raw = RawAttribute::from(&unknown);
let mut data: Vec<_> = raw.clone().into();
let len = data.len();
BigEndian::write_u16(&mut data[2..4], len as u16 - 4 - 1);
assert!(matches!(
UnknownAttributes::try_from(
&RawAttribute::from_bytes(data[..len - 1].as_ref()).unwrap()
),
Err(StunParseError::Truncated {
expected: 4,
actual: 3
})
));
}
#[test]
fn unknown_attributes_raw_wrong_type() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
let raw = RawAttribute::from(&unknown);
let mut data: Vec<_> = raw.clone().into();
BigEndian::write_u16(&mut data[0..2], 0);
assert!(matches!(
UnknownAttributes::try_from(&RawAttribute::from_bytes(data.as_ref()).unwrap()),
Err(StunParseError::WrongAttributeImplementation)
));
}
#[test]
fn unknown_attributes_write_into() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
let raw = RawAttribute::from(&unknown);
let mut dest = vec![0; raw.padded_len()];
unknown.write_into(&mut dest).unwrap();
tracing::error!("{dest:?}");
let raw = RawAttribute::from_bytes(&dest).unwrap();
let unknown2 = UnknownAttributes::try_from(&raw).unwrap();
assert!(unknown2.has_attribute(Realm::TYPE));
assert!(unknown2.has_attribute(AlternateServer::TYPE));
}
#[test]
#[should_panic(expected = "out of range")]
fn unknown_attributes_write_into_unchecked() {
let _log = crate::tests::test_init_log();
let mut unknown = UnknownAttributes::new(&[Realm::TYPE]);
unknown.add_attribute(AlternateServer::TYPE);
let raw = RawAttribute::from(&unknown);
let mut dest = vec![0; raw.padded_len() - 1];
unknown.write_into_unchecked(&mut dest);
}
}