#[cfg(feature = "std")]
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use core::convert::TryFrom;
#[cfg(feature = "std")]
use std::sync::{Mutex, OnceLock};
use byteorder::{BigEndian, ByteOrder};
use crate::attribute::*;
use tracing::{debug, trace, warn};
use hmac::digest::core_api::CoreWrapper;
use hmac::digest::CtOutput;
use hmac::HmacCore;
pub const MAGIC_COOKIE: u32 = 0x2112A442;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Method(u16);
impl core::fmt::Display for Method {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}({:#x}: {})", self.0, self.0, self.name())
}
}
#[cfg(feature = "std")]
static METHOD_NAME_MAP: OnceLock<Mutex<BTreeMap<Method, &'static str>>> = OnceLock::new();
impl Method {
#[cfg(feature = "std")]
pub fn add_name(self, name: &'static str) {
let mut mnames = METHOD_NAME_MAP
.get_or_init(Default::default)
.lock()
.unwrap();
mnames.insert(self, name);
}
pub const fn new(val: u16) -> Self {
if val >= 0xf000 {
panic!("Method value is out of range!");
}
Self(val)
}
pub fn value(&self) -> u16 {
self.0
}
pub fn name(self) -> &'static str {
match self {
BINDING => "BINDING",
_ => {
#[cfg(feature = "std")]
{
let mnames = METHOD_NAME_MAP
.get_or_init(Default::default)
.lock()
.unwrap();
if let Some(name) = mnames.get(&self) {
return name;
}
}
"unknown"
}
}
}
}
pub const BINDING: Method = Method::new(0x0001);
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StunParseError {
#[error("The provided data is not a STUN message")]
NotStun,
#[error("Not enough data available to parse the packet, expected {}, actual {}", .expected, .actual)]
Truncated {
expected: usize,
actual: usize,
},
#[error("Too many bytes for this data, expected {}, actual {}", .expected, .actual)]
TooLarge {
expected: usize,
actual: usize,
},
#[error("Missing attribute {}", .0)]
MissingAttribute(AttributeType),
#[error("An attribute {} was encountered after a message integrity attribute", .0)]
AttributeAfterIntegrity(AttributeType),
#[error("An attribute {} was encountered after a fingerprint attribute", .0)]
AttributeAfterFingerprint(AttributeType),
#[error("Fingerprint does not match")]
FingerprintMismatch,
#[error("The provided data does not match the message")]
DataMismatch,
#[error("The attribute contains invalid data")]
InvalidAttributeData,
#[error("Cannot parse with this attribute")]
WrongAttributeImplementation,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum StunWriteError {
#[error("The attribute already exists in the message")]
AttributeExists(AttributeType),
#[error("The message already contains a fingerprint attribute")]
FingerprintExists,
#[error("The message already contains a message intregrity attribute")]
MessageIntegrityExists,
#[error("Too many bytes for this data, expected {}, actual {}", .expected, .actual)]
TooLarge {
expected: usize,
actual: usize,
},
#[error("Not enough data available to parse the packet, expected {}, actual {}", .expected, .actual)]
TooSmall {
expected: usize,
actual: usize,
},
#[error("Failed to compute integrity")]
IntegrityFailed,
#[error("Out of range input provided")]
OutOfRange {
value: usize,
min: usize,
max: usize,
},
}
#[derive(Debug, thiserror::Error, Copy, Clone)]
pub enum ValidateError {
#[error("The message failed to parse the relevant integrity attributes")]
Parse(StunParseError),
#[error("The message failed integrity checks")]
IntegrityFailed,
}
impl From<StunParseError> for ValidateError {
fn from(value: StunParseError) -> Self {
Self::Parse(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct LongTermCredentials {
username: String,
password: String,
}
impl LongTermCredentials {
pub fn new(username: String, password: String) -> Self {
Self { username, password }
}
pub fn username(&self) -> &str {
&self.username
}
pub fn password(&self) -> &str {
&self.password
}
pub fn to_key(&self, realm: String) -> LongTermKeyCredentials {
LongTermKeyCredentials {
username: self.username.clone(),
password: self.password.clone(),
realm,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct LongTermKeyCredentials {
username: String,
password: String,
realm: String,
}
impl LongTermKeyCredentials {
pub fn new(username: String, password: String, realm: String) -> Self {
Self {
username,
password,
realm,
}
}
pub fn username(&self) -> &str {
&self.username
}
pub fn password(&self) -> &str {
&self.password
}
pub fn realm(&self) -> &str {
&self.realm
}
pub fn make_key(&self, algorithm: IntegrityAlgorithm) -> IntegrityKey {
match algorithm {
IntegrityAlgorithm::Sha1 => {
use md5::{Digest, Md5};
let mut digest = Md5::new();
digest.update(self.username.as_bytes());
digest.update(":".as_bytes());
digest.update(self.realm.as_bytes());
digest.update(":".as_bytes());
digest.update(self.password.as_bytes());
IntegrityKey::new_with_algo(IntegrityAlgorithm::Sha1, digest.finalize().to_vec())
}
IntegrityAlgorithm::Sha256 => {
use sha2::{Digest, Sha256};
let mut digest = Sha256::new();
digest.update(self.username.as_bytes());
digest.update(":".as_bytes());
digest.update(self.realm.as_bytes());
digest.update(":".as_bytes());
digest.update(self.password.as_bytes());
IntegrityKey::new_with_algo(IntegrityAlgorithm::Sha256, digest.finalize().to_vec())
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct ShortTermCredentials {
password: String,
}
impl ShortTermCredentials {
pub fn new(password: String) -> Self {
Self { password }
}
pub fn password(&self) -> &str {
&self.password
}
pub fn make_key(&self) -> IntegrityKey {
IntegrityKey::new(self.password.as_bytes().to_vec())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum MessageIntegrityCredentials {
ShortTerm(ShortTermCredentials),
LongTerm(LongTermKeyCredentials),
}
impl From<LongTermKeyCredentials> for MessageIntegrityCredentials {
fn from(value: LongTermKeyCredentials) -> Self {
MessageIntegrityCredentials::LongTerm(value)
}
}
impl From<ShortTermCredentials> for MessageIntegrityCredentials {
fn from(value: ShortTermCredentials) -> Self {
MessageIntegrityCredentials::ShortTerm(value)
}
}
impl MessageIntegrityCredentials {
pub fn make_key(&self, algorithm: IntegrityAlgorithm) -> IntegrityKey {
match self {
MessageIntegrityCredentials::ShortTerm(short) => short.make_key(),
MessageIntegrityCredentials::LongTerm(long) => long.make_key(algorithm),
}
}
}
type HmacSha1 = hmac::Hmac<sha1::Sha1>;
type HmacSha256 = hmac::Hmac<sha2::Sha256>;
#[derive(Debug, Clone)]
pub struct IntegrityKey {
key_algorithm: Option<IntegrityAlgorithm>,
bytes: Vec<u8>,
#[cfg(feature = "std")]
sha1: Arc<Mutex<Option<HmacSha1>>>,
#[cfg(feature = "std")]
sha256: Arc<Mutex<Option<HmacSha256>>>,
}
impl PartialEq<IntegrityKey> for IntegrityKey {
fn eq(&self, other: &Self) -> bool {
self.bytes == other.bytes
}
}
impl Eq for IntegrityKey {}
impl IntegrityKey {
fn new(bytes: Vec<u8>) -> Self {
Self {
key_algorithm: None,
bytes,
#[cfg(feature = "std")]
sha1: Default::default(),
#[cfg(feature = "std")]
sha256: Default::default(),
}
}
fn new_with_algo(key_algorithm: IntegrityAlgorithm, bytes: Vec<u8>) -> Self {
let mut ret = Self::new(bytes);
ret.key_algorithm = Some(key_algorithm);
ret
}
pub(crate) fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub(crate) fn verify_sha1(&self, data: &[&[u8]], expected: &[u8]) -> bool {
if self
.key_algorithm
.is_some_and(|algo| algo != IntegrityAlgorithm::Sha1)
{
return false;
}
use hmac::digest::Output;
let computed = self.compute_sha1(data);
#[allow(deprecated)]
{
computed == Output::<sha1::Sha1Core>::from_slice(expected).into()
}
}
pub(crate) fn compute_sha1(
&self,
data: &[&[u8]],
) -> CtOutput<CoreWrapper<HmacCore<CoreWrapper<sha1::Sha1Core>>>> {
use hmac::Mac;
#[cfg(feature = "std")]
let mut sha1 = self.sha1.lock().unwrap();
#[cfg(feature = "std")]
let hmac = sha1.get_or_insert_with(|| HmacSha1::new_from_slice(self.as_bytes()).unwrap());
#[cfg(not(feature = "std"))]
let mut hmac = HmacSha1::new_from_slice(self.as_bytes()).unwrap();
for data in data {
hmac.update(data);
}
#[cfg(feature = "std")]
let ret = hmac.finalize_reset();
#[cfg(not(feature = "std"))]
let ret = hmac.finalize();
ret
}
pub(crate) fn verify_sha256(&self, data: &[&[u8]], expected: &[u8]) -> bool {
use subtle::ConstantTimeEq;
if self
.key_algorithm
.is_some_and(|algo| algo != IntegrityAlgorithm::Sha256)
{
return false;
}
if expected.is_empty() {
return false;
}
let computed = self.compute_sha256(data);
if computed.len() < expected.len() {
return false;
}
computed[..expected.len()].ct_eq(expected).into()
}
pub(crate) fn compute_sha256(&self, data: &[&[u8]]) -> [u8; 32] {
trace!("computing sha256 using {data:?}");
use hmac::Mac;
#[cfg(feature = "std")]
let mut sha256 = self.sha256.lock().unwrap();
#[cfg(feature = "std")]
let hmac =
sha256.get_or_insert_with(|| HmacSha256::new_from_slice(self.as_bytes()).unwrap());
#[cfg(not(feature = "std"))]
let mut hmac = HmacSha256::new_from_slice(self.as_bytes()).unwrap();
for data in data {
hmac.update(data);
}
#[cfg(feature = "std")]
let ret = hmac.finalize_reset();
#[cfg(not(feature = "std"))]
let ret = hmac.finalize();
ret.into_bytes().into()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MessageClass {
Request,
Indication,
Success,
Error,
}
impl MessageClass {
pub fn is_response(self) -> bool {
matches!(self, MessageClass::Success | MessageClass::Error)
}
fn to_bits(self) -> u16 {
match self {
MessageClass::Request => 0x000,
MessageClass::Indication => 0x010,
MessageClass::Success => 0x100,
MessageClass::Error => 0x110,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct MessageType(u16);
impl core::fmt::Display for MessageType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"MessageType(class: {:?}, method: {}",
self.class(),
self.method(),
)
}
}
impl MessageType {
pub fn from_class_method(class: MessageClass, method: Method) -> Self {
let class_bits = MessageClass::to_bits(class);
let method = method.value();
let method_bits = method & 0xf | (method & 0x70) << 1 | (method & 0xf80) << 2;
Self(class_bits | method_bits)
}
pub fn class(self) -> MessageClass {
let class = (self.0 & 0x10) >> 4 | (self.0 & 0x100) >> 7;
match class {
0x0 => MessageClass::Request,
0x1 => MessageClass::Indication,
0x2 => MessageClass::Success,
0x3 => MessageClass::Error,
_ => unreachable!(),
}
}
pub fn has_class(self, cls: MessageClass) -> bool {
self.class() == cls
}
pub fn is_response(self) -> bool {
self.class().is_response()
}
pub fn method(self) -> Method {
Method::new(self.0 & 0xf | (self.0 & 0xe0) >> 1 | (self.0 & 0x3e00) >> 2)
}
pub fn has_method(self, method: Method) -> bool {
self.method() == method
}
pub fn write_into(&self, dest: &mut [u8]) {
BigEndian::write_u16(dest, self.0);
}
pub fn to_bytes(self) -> Vec<u8> {
let mut ret = vec![0; 2];
BigEndian::write_u16(&mut ret[0..2], self.0);
ret
}
pub fn from_bytes(data: &[u8]) -> Result<Self, StunParseError> {
let data = BigEndian::read_u16(data);
if data & 0xc000 != 0x0 {
return Err(StunParseError::NotStun);
}
Ok(Self(data))
}
}
impl TryFrom<&[u8]> for MessageType {
type Error = StunParseError;
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
MessageType::from_bytes(value)
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TransactionId {
id: u128,
}
impl TransactionId {
pub fn generate() -> TransactionId {
#[cfg(not(feature = "std"))]
{
use rand::TryRngCore;
let mut dest = [0; 16];
rand::rngs::OsRng
.try_fill_bytes(&mut dest)
.expect("Cannot generate random data");
u128::from_be_bytes(dest).into()
}
#[cfg(feature = "std")]
{
use rand::Rng;
let mut rng = rand::rng();
rng.random::<u128>().into()
}
}
}
impl From<u128> for TransactionId {
fn from(id: u128) -> Self {
Self {
id: id & 0xffff_ffff_ffff_ffff_ffff_ffff,
}
}
}
impl From<TransactionId> for u128 {
fn from(id: TransactionId) -> Self {
id.id
}
}
impl core::fmt::Display for TransactionId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#x}", self.id)
}
}
#[derive(Debug)]
pub struct MessageHeader {
mtype: MessageType,
transaction_id: TransactionId,
length: u16,
}
impl MessageHeader {
pub const LENGTH: usize = 20;
pub fn from_bytes(data: &[u8]) -> Result<Self, StunParseError> {
if data.len() < 20 {
return Err(StunParseError::Truncated {
expected: 20,
actual: data.len(),
});
}
let mtype = MessageType::from_bytes(data)?;
let mlength = BigEndian::read_u16(&data[2..]);
let tid = BigEndian::read_u128(&data[4..]);
let cookie = (tid >> 96) as u32;
if cookie != MAGIC_COOKIE {
warn!(
"malformed cookie constant {:?} != stored data {:?}",
MAGIC_COOKIE, cookie
);
return Err(StunParseError::NotStun);
}
Ok(Self {
mtype,
transaction_id: tid.into(),
length: mlength,
})
}
pub fn data_length(&self) -> u16 {
self.length
}
pub fn transaction_id(&self) -> TransactionId {
self.transaction_id
}
pub fn get_type(&self) -> MessageType {
self.mtype
}
fn new(mtype: MessageType, transaction_id: TransactionId, length: u16) -> Self {
Self {
mtype,
transaction_id,
length,
}
}
fn write_into(&self, dest: &mut [u8]) {
self.mtype.write_into(&mut dest[..2]);
let transaction: u128 = self.transaction_id.into();
let tid = (MAGIC_COOKIE as u128) << 96 | transaction & 0xffff_ffff_ffff_ffff_ffff_ffff;
BigEndian::write_u128(&mut dest[4..20], tid);
BigEndian::write_u16(&mut dest[2..4], self.length);
}
}
#[derive(Debug, Clone, Copy)]
pub struct Message<'a> {
data: &'a [u8],
}
impl core::fmt::Display for Message<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Message(class: {:?}, method: {}, transaction: {}, attributes: ",
self.get_type().class(),
self.get_type().method(),
self.transaction_id()
)?;
let iter = self.iter_attributes();
write!(f, "[")?;
for (i, (_offset, a)) in iter.enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{a}")?;
}
write!(f, "]")?;
write!(f, ")")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IntegrityAlgorithm {
Sha1,
Sha256,
}
impl<'a> Message<'a> {
pub fn builder<B: MessageWrite>(
mtype: MessageType,
transaction_id: TransactionId,
mut write: B,
) -> B {
let mut data = [0; 20];
MessageHeader::new(mtype, transaction_id, 0).write_into(&mut data);
write.push_data(&data);
write
}
pub fn builder_request<B: MessageWrite>(method: Method, write: B) -> B {
Message::builder(
MessageType::from_class_method(MessageClass::Request, method),
TransactionId::generate(),
write,
)
}
pub fn builder_success<B: MessageWrite>(orig: &Message, write: B) -> B {
if !orig.has_class(MessageClass::Request) {
panic!(
"A success response message was attempted to be created from a non-request message"
);
}
Message::builder(
MessageType::from_class_method(MessageClass::Success, orig.method()),
orig.transaction_id(),
write,
)
}
pub fn builder_error<B: MessageWrite>(orig: &Message, write: B) -> B {
if !orig.has_class(MessageClass::Request) {
panic!(
"An error response message was attempted to be created from a non-request message"
);
}
Message::builder(
MessageType::from_class_method(MessageClass::Error, orig.method()),
orig.transaction_id(),
write,
)
}
pub fn builder_indication<B: MessageWrite>(method: Method, write: B) -> B {
Message::builder(
MessageType::from_class_method(MessageClass::Indication, method),
TransactionId::generate(),
write,
)
}
pub fn get_type(&self) -> MessageType {
MessageType::try_from(&self.data[..2]).unwrap()
}
pub fn class(&self) -> MessageClass {
self.get_type().class()
}
pub fn has_class(&self, cls: MessageClass) -> bool {
self.class() == cls
}
pub fn is_response(&self) -> bool {
self.class().is_response()
}
pub fn method(&self) -> Method {
self.get_type().method()
}
pub fn has_method(&self, method: Method) -> bool {
self.method() == method
}
pub fn transaction_id(&self) -> TransactionId {
BigEndian::read_u128(&self.data[4..]).into()
}
#[tracing::instrument(
name = "message_from_bytes",
level = "trace",
skip(data),
fields(
data.len = data.len()
)
)]
pub fn from_bytes(data: &'a [u8]) -> Result<Self, StunParseError> {
let orig_data = data;
let header = MessageHeader::from_bytes(data)?;
let mlength = header.data_length() as usize;
if mlength + MessageHeader::LENGTH > data.len() {
warn!(
"malformed advertised size {} and data size {} don't match",
mlength + 20,
data.len()
);
return Err(StunParseError::Truncated {
expected: mlength + MessageHeader::LENGTH,
actual: data.len(),
});
}
let ending_attributes = [
MessageIntegrity::TYPE,
MessageIntegritySha256::TYPE,
Fingerprint::TYPE,
];
let mut seen_ending_attributes = [AttributeType::new(0); 3];
let mut seen_ending_len = 0;
let mut data_offset = MessageHeader::LENGTH;
for attr in MessageRawAttributesIter::new(data) {
let (_offset, attr) = attr.inspect_err(|e| {
warn!("failed to parse message attribute at offset {data_offset}: {e}",);
})?;
if seen_ending_len > 0 && !ending_attributes.contains(&attr.get_type()) {
if seen_ending_attributes.contains(&Fingerprint::TYPE) {
warn!("unexpected attribute {} after FINGERPRINT", attr.get_type());
return Ok(Message { data: orig_data });
} else {
warn!(
"unexpected attribute {} after MESSAGE_INTEGRITY",
attr.get_type()
);
return Ok(Message { data: orig_data });
}
}
if ending_attributes.contains(&attr.get_type()) {
if seen_ending_attributes.contains(&attr.get_type()) {
if seen_ending_attributes.contains(&Fingerprint::TYPE) {
warn!("unexpected attribute {} after FINGERPRINT", attr.get_type());
return Ok(Message { data: orig_data });
} else {
warn!(
"unexpected attribute {} after MESSAGE_INTEGRITY",
attr.get_type()
);
return Ok(Message { data: orig_data });
}
} else {
seen_ending_attributes[seen_ending_len] = attr.get_type();
seen_ending_len += 1;
}
}
let padded_len = attr.padded_len();
if attr.get_type() == Fingerprint::TYPE {
let f = Fingerprint::from_raw_ref(&attr)?;
let msg_fingerprint = f.fingerprint();
let mut header = [0; 4];
header[0] = orig_data[0];
header[1] = orig_data[1];
BigEndian::write_u16(
&mut header[2..4],
(data_offset + padded_len - MessageHeader::LENGTH) as u16,
);
let fingerprint_data = &orig_data[4..data_offset];
let calculated_fingerprint = Fingerprint::compute(&[&header, fingerprint_data]);
if &calculated_fingerprint != msg_fingerprint {
warn!(
"fingerprint mismatch {:?} != {:?}",
calculated_fingerprint, msg_fingerprint
);
return Err(StunParseError::FingerprintMismatch);
}
}
data_offset += padded_len;
}
Ok(Message { data: orig_data })
}
pub fn validate_integrity(
&self,
credentials: &MessageIntegrityCredentials,
) -> Result<IntegrityAlgorithm, ValidateError> {
let raw_sha1 = self.raw_attribute(MessageIntegrity::TYPE);
let raw_sha256 = self.raw_attribute(MessageIntegritySha256::TYPE);
let (algo, msg_hmac) = match (raw_sha1, raw_sha256) {
(_, Some(sha256)) => {
let integrity = MessageIntegritySha256::try_from(&sha256)?;
(IntegrityAlgorithm::Sha256, integrity.hmac().to_vec())
}
(Some(sha1), None) => {
let integrity = MessageIntegrity::try_from(&sha1)?;
(IntegrityAlgorithm::Sha1, integrity.hmac().to_vec())
}
(None, None) => {
return Err(StunParseError::MissingAttribute(MessageIntegrity::TYPE).into())
}
};
let key = credentials.make_key(algo);
self.validate_integrity_with_hmac(algo, &msg_hmac, &key)
}
pub fn validate_integrity_with_key(
&self,
key: &IntegrityKey,
) -> Result<IntegrityAlgorithm, ValidateError> {
let raw_sha1 = self.raw_attribute(MessageIntegrity::TYPE);
let raw_sha256 = self.raw_attribute(MessageIntegritySha256::TYPE);
let (algo, msg_hmac) = if let Some(algo) = key.key_algorithm {
match (algo, raw_sha1, raw_sha256) {
(IntegrityAlgorithm::Sha256, _, Some(sha256)) => {
let integrity = MessageIntegritySha256::try_from(&sha256)?;
(algo, integrity.hmac().to_vec())
}
(IntegrityAlgorithm::Sha1, Some(sha1), _) => {
let integrity = MessageIntegrity::try_from(&sha1)?;
(algo, integrity.hmac().to_vec())
}
_ => return Err(StunParseError::MissingAttribute(MessageIntegrity::TYPE).into()),
}
} else {
match (raw_sha1, raw_sha256) {
(_, Some(sha256)) => {
let integrity = MessageIntegritySha256::try_from(&sha256)?;
(IntegrityAlgorithm::Sha256, integrity.hmac().to_vec())
}
(Some(sha1), None) => {
let integrity = MessageIntegrity::try_from(&sha1)?;
(IntegrityAlgorithm::Sha1, integrity.hmac().to_vec())
}
(None, None) => {
return Err(StunParseError::MissingAttribute(MessageIntegrity::TYPE).into())
}
}
};
self.validate_integrity_with_hmac(algo, &msg_hmac, key)
}
#[tracing::instrument(
name = "message_validate_integrity_with_hmac",
level = "trace",
skip(self, key, msg_hmac),
fields(
msg.transaction = %self.transaction_id(),
)
)]
fn validate_integrity_with_hmac(
&self,
algo: IntegrityAlgorithm,
msg_hmac: &[u8],
key: &IntegrityKey,
) -> Result<IntegrityAlgorithm, ValidateError> {
if key.key_algorithm.is_some_and(|key_algo| key_algo != algo) {
debug!(
"Key algorithm ({:?}) does not match algo ({algo:?})",
key.key_algorithm
);
return Err(StunParseError::DataMismatch.into());
}
let data = self.data;
debug_assert!(data.len() >= MessageHeader::LENGTH);
let mut data = &data[MessageHeader::LENGTH..];
let mut data_offset = MessageHeader::LENGTH;
while !data.is_empty() {
let attr = RawAttribute::from_bytes(data)?;
if algo == IntegrityAlgorithm::Sha1 && attr.get_type() == MessageIntegrity::TYPE {
let msg = MessageIntegrity::try_from(&attr)?;
debug_assert!(msg.hmac().as_slice() == msg_hmac);
let mut header = [0; 4];
header[0] = self.data[0];
header[1] = self.data[1];
let hmac_data = &self.data[4..data_offset];
BigEndian::write_u16(
&mut header[2..4],
data_offset as u16 + 24 - MessageHeader::LENGTH as u16,
);
if MessageIntegrity::verify(
&[header.as_slice(), hmac_data],
key,
msg_hmac.try_into().unwrap(),
) {
return Ok(algo);
} else {
return Err(ValidateError::IntegrityFailed);
}
} else if algo == IntegrityAlgorithm::Sha256
&& attr.get_type() == MessageIntegritySha256::TYPE
{
let msg = MessageIntegritySha256::try_from(&attr)?;
debug_assert!(msg.hmac() == msg_hmac);
let mut header = [0; 4];
header[0] = self.data[0];
header[1] = self.data[1];
let hmac_data = &self.data[4..data_offset];
BigEndian::write_u16(
&mut header[2..4],
data_offset as u16 + attr.padded_len() as u16 - MessageHeader::LENGTH as u16,
);
if MessageIntegritySha256::verify(&[&header, hmac_data], key, msg_hmac) {
return Ok(algo);
} else {
return Err(ValidateError::IntegrityFailed);
}
}
let padded_len = attr.padded_len();
debug_assert!(padded_len <= data.len());
data = &data[padded_len..];
data_offset += padded_len;
}
unreachable!();
}
pub fn nth_raw_attribute(&self, atype: AttributeType, n: usize) -> Option<RawAttribute<'_>> {
self.nth_raw_attribute_and_offset(atype, n)
.map(|(_offset, attr)| attr)
}
pub fn raw_attribute(&self, atype: AttributeType) -> Option<RawAttribute<'_>> {
self.nth_raw_attribute(atype, 0)
}
#[tracing::instrument(
name = "message_nth_raw_attribute_and_offset",
level = "trace",
skip(self, atype),
fields(
msg.transaction = %self.transaction_id(),
attribute_type = %atype,
)
)]
pub fn nth_raw_attribute_and_offset(
&self,
atype: AttributeType,
n: usize,
) -> Option<(usize, RawAttribute<'_>)> {
if let Some((offset, attr)) = self
.iter_attributes()
.filter(|(_offset, attr)| attr.get_type() == atype)
.nth(n)
{
trace!("found attribute at offset: {offset}");
Some((offset, attr))
} else {
trace!("could not find attribute");
None
}
}
pub fn raw_attribute_and_offset(
&self,
atype: AttributeType,
) -> Option<(usize, RawAttribute<'_>)> {
self.nth_raw_attribute_and_offset(atype, 0)
}
pub fn nth_attribute<A: AttributeFromRaw<'a> + AttributeStaticType>(
&'a self,
n: usize,
) -> Result<A, StunParseError> {
self.nth_attribute_and_offset(n).map(|(_offset, attr)| attr)
}
pub fn attribute<A: AttributeFromRaw<'a> + AttributeStaticType>(
&'a self,
) -> Result<A, StunParseError> {
self.nth_attribute(0)
}
pub fn nth_attribute_and_offset<A: AttributeFromRaw<'a> + AttributeStaticType>(
&'a self,
n: usize,
) -> Result<(usize, A), StunParseError> {
self.nth_raw_attribute_and_offset(A::TYPE, n)
.ok_or(StunParseError::MissingAttribute(A::TYPE))
.and_then(|(offset, raw)| A::from_raw(raw).map(|attr| (offset, attr)))
}
pub fn attribute_and_offset<A: AttributeFromRaw<'a> + AttributeStaticType>(
&'a self,
) -> Result<(usize, A), StunParseError> {
self.nth_attribute_and_offset(0)
}
pub fn iter_attributes(&self) -> impl Iterator<Item = (usize, RawAttribute<'_>)> {
MessageAttributesIter::new(self.data)
}
#[tracing::instrument(
level = "trace",
skip(msg, write),
fields(
msg.transaction = %msg.transaction_id(),
)
)]
pub fn check_attribute_types<B: MessageWrite>(
msg: &Message,
supported: &[AttributeType],
required_in_msg: &[AttributeType],
write: B,
) -> Option<B> {
let unsupported: Vec<AttributeType> = msg
.iter_attributes()
.map(|(_offset, a)| a.get_type())
.filter(|at| at.comprehension_required() && !supported.contains(at))
.collect();
if !unsupported.is_empty() {
warn!(
"Message contains unknown comprehension required attributes {:?}, returning unknown attributes",
unsupported
);
return Some(Message::unknown_attributes(msg, &unsupported, write));
}
let has_required_attribute_missing = required_in_msg
.iter()
.any(|&at| {
!msg.iter_attributes()
.map(|(_offset, a)| a.get_type())
.any(|a| a == at)
});
if has_required_attribute_missing {
warn!("Message is missing required attributes, returning bad request");
return Some(Message::bad_request(msg, write));
}
None
}
pub fn unknown_attributes<B: MessageWrite>(
src: &Message,
attributes: &[AttributeType],
write: B,
) -> B {
let mut out = Message::builder_error(src, write);
let software = Software::new("stun-types").unwrap();
out.add_attribute(&software).unwrap();
let error = ErrorCode::new(420, "Unknown Attributes").unwrap();
out.add_attribute(&error).unwrap();
let unknown = UnknownAttributes::new(attributes);
if !attributes.is_empty() {
out.add_attribute(&unknown).unwrap();
}
out
}
pub fn bad_request<B: MessageWrite>(src: &Message, write: B) -> B {
let mut out = Message::builder_error(src, write);
let software = Software::new("stun-types").unwrap();
out.add_attribute(&software).unwrap();
let error = ErrorCode::new(400, "Bad Request").unwrap();
out.add_attribute(&error).unwrap();
out
}
pub fn has_attribute(&self, atype: AttributeType) -> bool {
self.iter_attributes()
.any(|(_offset, attr)| attr.get_type() == atype)
}
pub fn as_bytes(&self) -> &[u8] {
self.data
}
}
impl<'a> TryFrom<&'a [u8]> for Message<'a> {
type Error = StunParseError;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
Message::from_bytes(value)
}
}
impl AsRef<[u8]> for Message<'_> {
fn as_ref(&self) -> &[u8] {
self.data
}
}
#[derive(Debug)]
struct MessageRawAttributesIter<'a> {
data: &'a [u8],
data_i: usize,
}
impl<'a> MessageRawAttributesIter<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
data_i: MessageHeader::LENGTH,
}
}
}
impl<'a> Iterator for MessageRawAttributesIter<'a> {
type Item = Result<(usize, RawAttribute<'a>), StunParseError>;
fn next(&mut self) -> Option<Self::Item> {
if self.data_i >= self.data.len() {
return None;
}
match RawAttribute::from_bytes(&self.data[self.data_i..]) {
Ok(attr) => {
let padded_len = attr.padded_len();
self.data_i += padded_len;
if self.data_i > self.data.len() {
warn!(
"attribute {} extends past the end of the data",
attr.get_type()
);
return Some(Err(StunParseError::Truncated {
expected: self.data_i,
actual: self.data.len(),
}));
}
Some(Ok((self.data_i - padded_len, attr)))
}
Err(e) => {
let offset = self.data_i;
self.data_i = self.data.len();
let e = match e {
StunParseError::Truncated { expected, actual } => StunParseError::Truncated {
expected: expected + 4 + offset,
actual: actual + 4 + offset,
},
StunParseError::TooLarge { expected, actual } => StunParseError::TooLarge {
expected: expected + 4 + offset,
actual: actual + 4 + offset,
},
e => e,
};
Some(Err(e))
}
}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct MessageAttributesIter<'a> {
header_parsed: bool,
inner: MessageRawAttributesIter<'a>,
last_attr_type: AttributeType,
seen_message_integrity: bool,
}
impl<'a> MessageAttributesIter<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self {
header_parsed: false,
inner: MessageRawAttributesIter::new(data),
seen_message_integrity: false,
last_attr_type: AttributeType::new(0),
}
}
}
impl<'a> Iterator for MessageAttributesIter<'a> {
type Item = (usize, RawAttribute<'a>);
fn next(&mut self) -> Option<Self::Item> {
if self.last_attr_type == Fingerprint::TYPE {
return None;
}
if !self.header_parsed {
let Ok(hdr) = MessageHeader::from_bytes(self.inner.data) else {
self.last_attr_type = Fingerprint::TYPE;
return None;
};
if hdr.data_length() as usize + MessageHeader::LENGTH > self.inner.data.len() {
self.last_attr_type = Fingerprint::TYPE;
return None;
}
self.header_parsed = true;
}
let (offset, attr) = self.inner.next()?.ok()?;
let attr_type = attr.get_type();
if self.seen_message_integrity {
if self.last_attr_type != Fingerprint::TYPE && attr_type == Fingerprint::TYPE {
self.last_attr_type = attr_type;
return Some((offset, attr));
}
if self.last_attr_type == MessageIntegrity::TYPE
&& attr_type == MessageIntegritySha256::TYPE
{
self.last_attr_type = attr_type;
return Some((offset, attr));
}
self.last_attr_type = Fingerprint::TYPE;
return None;
}
if attr.get_type() == MessageIntegrity::TYPE
|| attr.get_type() == MessageIntegritySha256::TYPE
|| attr.get_type() == Fingerprint::TYPE
{
self.seen_message_integrity = true;
}
self.last_attr_type = attr.get_type();
Some((offset, attr))
}
}
#[allow(clippy::len_without_is_empty)]
pub trait MessageWrite {
type Output;
fn max_size(&self) -> Option<usize> {
None
}
fn mut_data(&mut self) -> &mut [u8];
fn data(&self) -> &[u8];
fn len(&self) -> usize;
fn push_data(&mut self, data: &[u8]);
fn push_attribute_unchecked(&mut self, attr: &dyn AttributeWrite);
fn has_attribute(&self, atype: AttributeType) -> bool;
fn has_any_attribute(&self, atypes: &[AttributeType]) -> Option<AttributeType>;
fn finish(self) -> Self::Output;
}
pub trait MessageWriteExt: MessageWrite {
fn get_type(&self) -> MessageType {
MessageType::from_bytes(self.data()).unwrap()
}
fn class(&self) -> MessageClass {
self.get_type().class()
}
fn has_class(&self, cls: MessageClass) -> bool {
self.class() == cls
}
fn is_response(&self) -> bool {
self.class().is_response()
}
fn method(&self) -> Method {
self.get_type().method()
}
fn has_method(&self, method: Method) -> bool {
self.method() == method
}
fn transaction_id(&self) -> TransactionId {
BigEndian::read_u128(&self.data()[4..]).into()
}
fn add_message_integrity(
&mut self,
credentials: &MessageIntegrityCredentials,
algorithm: IntegrityAlgorithm,
) -> Result<(), StunWriteError> {
let key = credentials.make_key(algorithm);
self.add_message_integrity_with_key(&key, algorithm)
}
#[tracing::instrument(
name = "message_add_integrity_with_key",
level = "trace",
err,
skip(self),
fields(
msg.transaction = %self.transaction_id(),
)
)]
fn add_message_integrity_with_key(
&mut self,
key: &IntegrityKey,
algorithm: IntegrityAlgorithm,
) -> Result<(), StunWriteError> {
let mut atypes = [AttributeType::new(0); 3];
let mut i = 0;
atypes[i] = match algorithm {
IntegrityAlgorithm::Sha1 => MessageIntegrity::TYPE,
IntegrityAlgorithm::Sha256 => MessageIntegritySha256::TYPE,
};
i += 1;
if algorithm == IntegrityAlgorithm::Sha1 {
atypes[i] = MessageIntegritySha256::TYPE;
i += 1;
}
atypes[i] = Fingerprint::TYPE;
i += 1;
match self.has_any_attribute(&atypes[..i]) {
Some(MessageIntegrity::TYPE) => {
return Err(StunWriteError::AttributeExists(MessageIntegrity::TYPE))
}
Some(MessageIntegritySha256::TYPE) => {
return Err(StunWriteError::AttributeExists(
MessageIntegritySha256::TYPE,
));
}
Some(Fingerprint::TYPE) => return Err(StunWriteError::FingerprintExists),
_ => (),
}
match algorithm {
IntegrityAlgorithm::Sha1 => {
check_attribute_can_fit(self, &MessageIntegrity::new([0; 20]))?
}
IntegrityAlgorithm::Sha256 => {
check_attribute_can_fit(self, &MessageIntegritySha256::new(&[0; 32]).unwrap())?
}
};
add_message_integrity_unchecked(self, key, algorithm);
Ok(())
}
#[tracing::instrument(
name = "message_add_fingerprint",
level = "trace",
skip(self),
fields(
msg.transaction = %self.transaction_id(),
)
)]
fn add_fingerprint(&mut self) -> Result<(), StunWriteError> {
if self.has_attribute(Fingerprint::TYPE) {
return Err(StunWriteError::AttributeExists(Fingerprint::TYPE));
}
check_attribute_can_fit(self, &Fingerprint::new([0; 4]))?;
add_fingerprint_unchecked(self);
Ok(())
}
#[tracing::instrument(
name = "message_add_attribute",
level = "trace",
err,
skip(self, attr),
fields(
msg.transaction = %self.transaction_id(),
)
)]
fn add_attribute(&mut self, attr: &dyn AttributeWrite) -> Result<(), StunWriteError> {
let ty = attr.get_type();
match ty {
MessageIntegrity::TYPE => {
panic!("Cannot write MessageIntegrity with `add_attribute`. Use add_message_integrity() instead");
}
MessageIntegritySha256::TYPE => {
panic!("Cannot write MessageIntegritySha256 with `add_attribute`. Use add_message_integrity() instead");
}
Fingerprint::TYPE => {
panic!(
"Cannot write Fingerprint with `add_attribute`. Use add_fingerprint() instead"
);
}
_ => (),
}
match self.has_any_attribute(&[
MessageIntegrity::TYPE,
MessageIntegritySha256::TYPE,
Fingerprint::TYPE,
]) {
Some(MessageIntegrity::TYPE) => return Err(StunWriteError::MessageIntegrityExists),
Some(MessageIntegritySha256::TYPE) => {
return Err(StunWriteError::MessageIntegrityExists)
}
Some(Fingerprint::TYPE) => return Err(StunWriteError::FingerprintExists),
_ => (),
}
check_attribute_can_fit(self, attr)?;
self.push_attribute_unchecked(attr);
Ok(())
}
}
impl<T: MessageWrite> MessageWriteExt for T {}
#[derive(Debug, Default)]
pub struct MessageWriteVec {
output: Vec<u8>,
attributes: smallvec::SmallVec<[AttributeType; 16]>,
}
impl MessageWriteVec {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
output: Vec::with_capacity(capacity),
attributes: Default::default(),
}
}
}
impl core::ops::Deref for MessageWriteVec {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.output
}
}
impl core::ops::DerefMut for MessageWriteVec {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.output
}
}
impl MessageWrite for MessageWriteVec {
type Output = Vec<u8>;
fn mut_data(&mut self) -> &mut [u8] {
&mut self.output
}
fn data(&self) -> &[u8] {
&self.output
}
fn len(&self) -> usize {
self.output.len()
}
fn push_data(&mut self, data: &[u8]) {
self.output.extend(data)
}
fn finish(self) -> Self::Output {
self.output
}
fn push_attribute_unchecked(&mut self, attr: &dyn AttributeWrite) {
let offset = self.output.len();
let padded_len = attr.padded_len();
let expected = offset + padded_len;
BigEndian::write_u16(
&mut self.output[2..4],
(expected - MessageHeader::LENGTH) as u16,
);
self.output.resize(expected, 0);
attr.write_into_unchecked(&mut self.output[offset..]);
self.attributes.push(attr.get_type());
}
fn has_attribute(&self, atype: AttributeType) -> bool {
self.attributes.contains(&atype)
}
fn has_any_attribute(&self, atypes: &[AttributeType]) -> Option<AttributeType> {
self.attributes
.iter()
.find(|&typ| atypes.contains(typ))
.cloned()
}
}
#[derive(Debug, Default)]
pub struct MessageWriteMutSlice<'a> {
output: &'a mut [u8],
offset: usize,
attributes: smallvec::SmallVec<[AttributeType; 16]>,
}
impl<'a> MessageWriteMutSlice<'a> {
pub fn new(data: &'a mut [u8]) -> Self {
Self {
output: data,
offset: 0,
attributes: Default::default(),
}
}
}
impl core::ops::Deref for MessageWriteMutSlice<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.output
}
}
impl core::ops::DerefMut for MessageWriteMutSlice<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.output
}
}
impl<'a> MessageWrite for MessageWriteMutSlice<'a> {
type Output = usize;
fn max_size(&self) -> Option<usize> {
Some(self.output.len())
}
fn mut_data(&mut self) -> &mut [u8] {
&mut self.output[..self.offset]
}
fn data(&self) -> &[u8] {
&self.output[..self.offset]
}
fn len(&self) -> usize {
self.offset
}
fn push_data(&mut self, data: &[u8]) {
let len = data.len();
self.output[self.offset..self.offset + len].copy_from_slice(data);
self.offset += len;
}
fn push_attribute_unchecked(&mut self, attr: &dyn AttributeWrite) {
let padded_len = attr.padded_len();
let expected = self.offset + padded_len;
BigEndian::write_u16(
&mut self.output[2..4],
(expected - MessageHeader::LENGTH) as u16,
);
self.attributes.push(attr.get_type());
attr.write_into(&mut self.output[self.offset..self.offset + padded_len])
.unwrap();
self.offset += padded_len;
}
fn finish(self) -> Self::Output {
self.offset
}
fn has_attribute(&self, atype: AttributeType) -> bool {
self.attributes.contains(&atype)
}
fn has_any_attribute(&self, atypes: &[AttributeType]) -> Option<AttributeType> {
self.attributes
.iter()
.find(|&typ| atypes.contains(typ))
.cloned()
}
}
fn check_attribute_can_fit<O, T: MessageWrite<Output = O> + ?Sized>(
this: &mut T,
attr: &dyn AttributeWrite,
) -> Result<usize, StunWriteError> {
let len = attr.padded_len();
let out_data = this.data();
if out_data.len() < MessageHeader::LENGTH {
return Err(StunWriteError::TooSmall {
expected: 20,
actual: out_data.len(),
});
}
let expected = BigEndian::read_u16(&out_data[2..4]) as usize + MessageHeader::LENGTH + len;
if let Some(max) = this.max_size() {
if max < expected {
return Err(StunWriteError::TooSmall {
expected,
actual: max,
});
}
}
Ok(expected)
}
fn add_message_integrity_unchecked<O, T: MessageWrite<Output = O> + ?Sized>(
this: &mut T,
key: &IntegrityKey,
algorithm: IntegrityAlgorithm,
) {
match algorithm {
IntegrityAlgorithm::Sha1 => {
this.push_attribute_unchecked(&MessageIntegrity::new([0; 20]));
let len = this.len();
let data = this.mut_data();
let integrity = MessageIntegrity::compute(&[&data[..len - 24]], key).unwrap();
data[len - 20..].copy_from_slice(&integrity);
}
IntegrityAlgorithm::Sha256 => {
this.push_attribute_unchecked(&MessageIntegritySha256::new(&[0; 32]).unwrap());
let len = this.len();
let data = this.mut_data();
let integrity = MessageIntegritySha256::compute(&[&data[..len - 36]], key).unwrap();
data[len - 32..].copy_from_slice(&integrity);
}
}
}
fn add_fingerprint_unchecked<O, T: MessageWrite<Output = O> + ?Sized>(this: &mut T) {
this.push_attribute_unchecked(&Fingerprint::new([0; 4]));
let len = this.len();
let data = this.mut_data();
let fingerprint = Fingerprint::compute(&[&data[..len - 8]]);
let fingerprint = Fingerprint::new(fingerprint);
fingerprint.write_into(&mut data[len - 8..]).unwrap();
}
#[cfg(test)]
mod tests {
use alloc::borrow::ToOwned;
use precis_profiles::precis_core::profile::Profile;
use tracing::error;
use super::*;
#[test]
#[should_panic(expected = "Method value is out of range")]
fn method_value_out_of_range() {
let _log = crate::tests::test_init_log();
Method::new(0xf000);
}
#[test]
#[cfg(feature = "std")]
fn method_name() {
let _log = crate::tests::test_init_log();
assert_eq!(BINDING.name(), "BINDING");
let method = Method::new(0x111);
method.add_name("SOME-NAME");
assert_eq!(method.name(), "SOME-NAME");
assert_eq!(Method::new(0x112).name(), "unknown");
}
#[test]
fn msg_type_roundtrip() {
let _log = crate::tests::test_init_log();
for m in 0..0xfff {
let m = Method::new(m);
let classes = vec![
MessageClass::Request,
MessageClass::Indication,
MessageClass::Success,
MessageClass::Error,
];
for c in classes {
let mtype = MessageType::from_class_method(c, m);
trace!("{mtype}");
assert_eq!(mtype.class(), c);
assert!(mtype.has_class(c));
assert_eq!(mtype.method(), m);
assert!(mtype.has_method(m));
let bytes = mtype.to_bytes();
let ptype = MessageType::from_bytes(&bytes).unwrap();
assert_eq!(mtype, ptype);
}
}
}
#[test]
fn msg_type_not_stun() {
assert!(matches!(
MessageType::from_bytes(&[0xc0, 0x00]),
Err(StunParseError::NotStun)
));
}
#[test]
fn msg_roundtrip() {
let _log = crate::tests::test_init_log();
for m in (0x009..0x4ff).step_by(0x123) {
let m = Method::new(m);
let classes = vec![
MessageClass::Request,
MessageClass::Indication,
MessageClass::Success,
MessageClass::Error,
];
for c in classes {
let mtype = MessageType::from_class_method(c, m);
for tid in (0x18..0xff_ffff_ffff_ffff_ffff).step_by(0xfedc_ba98_7654_3210) {
let mut msg = Message::builder(mtype, tid.into(), MessageWriteVec::default());
let attr = RawAttribute::new(1.into(), &[3]);
assert!(msg.add_attribute(&attr).is_ok());
let data = msg.finish();
let msg = Message::from_bytes(&data).unwrap();
let msg_attr = msg.raw_attribute(1.into()).unwrap();
assert_eq!(msg_attr, attr);
assert_eq!(msg.get_type(), mtype);
assert_eq!(msg.transaction_id(), tid.into());
assert_eq!(msg.as_bytes(), &data);
assert_eq!(msg.as_ref(), &data);
}
}
}
}
#[test]
fn unknown_attributes() {
let _log = crate::tests::test_init_log();
let src = Message::builder_request(BINDING, MessageWriteVec::default()).finish();
let src = Message::from_bytes(&src).unwrap();
let msg =
Message::unknown_attributes(&src, &[Software::TYPE], MessageWriteVec::new()).finish();
let msg = Message::from_bytes(&msg).unwrap();
assert_eq!(msg.transaction_id(), src.transaction_id());
assert_eq!(msg.class(), MessageClass::Error);
assert!(msg.has_class(MessageClass::Error));
assert!(!msg.has_class(MessageClass::Success));
assert_eq!(msg.method(), src.method());
assert!(msg.has_method(src.method()));
let err = msg.attribute::<ErrorCode>().unwrap();
assert_eq!(err.code(), 420);
let unknown_attrs = msg.attribute::<UnknownAttributes>().unwrap();
assert!(unknown_attrs.has_attribute(Software::TYPE));
}
#[test]
fn bad_request() {
let _log = crate::tests::test_init_log();
let src = Message::builder_request(BINDING, MessageWriteVec::new()).finish();
let src = Message::from_bytes(&src).unwrap();
let msg = Message::bad_request(&src, MessageWriteVec::new());
assert!(msg.has_class(MessageClass::Error));
assert!(!msg.has_class(MessageClass::Success));
assert!(msg.has_method(src.method()));
assert!(!msg.has_method(Method::new(0x111)));
assert!(msg.is_response());
let msg = msg.finish();
let msg = Message::from_bytes(&msg).unwrap();
assert_eq!(msg.transaction_id(), src.transaction_id());
assert_eq!(msg.class(), MessageClass::Error);
assert_eq!(msg.method(), src.method());
let err = msg.attribute::<ErrorCode>().unwrap();
assert_eq!(err.code(), 400);
}
#[test]
fn fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
assert!(msg.has_class(MessageClass::Request));
assert!(!msg.has_class(MessageClass::Success));
assert!(msg.has_method(BINDING));
assert!(!msg.has_method(Method::new(0x111)));
let software = Software::new("s").unwrap();
msg.add_attribute(&software).unwrap();
msg.add_fingerprint().unwrap();
let bytes = msg.finish();
let new_msg = Message::from_bytes(&bytes).unwrap();
let (offset, software) = new_msg.attribute_and_offset::<Software>().unwrap();
assert_eq!(software.software(), "s");
assert_eq!(offset, 20);
let (offset, _new_fingerprint) =
new_msg.raw_attribute_and_offset(Fingerprint::TYPE).unwrap();
assert_eq!(offset, 28);
}
#[test]
fn integrity() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let software = Software::new("s").unwrap();
msg.add_attribute(&software).unwrap();
msg.add_message_integrity(&credentials, algorithm).unwrap();
let bytes = msg.finish();
let new_msg = Message::from_bytes(&bytes).unwrap();
new_msg.validate_integrity(&credentials).unwrap();
let (offset, software) = new_msg.attribute_and_offset::<Software>().unwrap();
assert_eq!(software.software(), "s");
assert_eq!(offset, 20);
}
}
#[test]
fn write_into_short_destination() {
let _log = crate::tests::test_init_log();
const LEN: usize = MessageHeader::LENGTH + 8;
let mut data = [0; LEN - 1];
let mut msg = Message::builder_request(BINDING, MessageWriteMutSlice::new(&mut data));
let software = Software::new("s").unwrap();
assert!(
matches!(msg.add_attribute(&software), Err(StunWriteError::TooSmall { expected, actual }) if expected == LEN && actual == LEN - 1)
);
}
#[test]
fn integrity_key() {
let _log = crate::tests::test_init_log();
let credentials1 = ShortTermCredentials::new("pass1".to_owned());
let key1 =
MessageIntegrityCredentials::from(credentials1).make_key(IntegrityAlgorithm::Sha1);
let credentials2 = ShortTermCredentials::new("pass2".to_owned());
let key2 =
MessageIntegrityCredentials::from(credentials2).make_key(IntegrityAlgorithm::Sha1);
assert_eq!(key1, key1);
assert_eq!(key2, key2);
assert_ne!(key1, key2);
}
#[test]
fn add_duplicate_integrity() {
let _log = crate::tests::test_init_log();
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1)
.unwrap();
assert!(matches!(
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1),
Err(StunWriteError::AttributeExists(MessageIntegrity::TYPE))
));
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha256)
.unwrap();
assert!(matches!(
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha256),
Err(StunWriteError::AttributeExists(
MessageIntegritySha256::TYPE
))
));
let software = Software::new("s").unwrap();
assert!(matches!(
msg.add_attribute(&software),
Err(StunWriteError::MessageIntegrityExists)
));
}
#[test]
fn add_sha1_integrity_after_sha256() {
let _log = crate::tests::test_init_log();
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha256)
.unwrap();
assert!(matches!(
msg.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1),
Err(StunWriteError::AttributeExists(
MessageIntegritySha256::TYPE
))
));
}
#[test]
fn add_attribute_after_integrity() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, algorithm).unwrap();
let software = Software::new("s").unwrap();
assert!(matches!(
msg.add_attribute(&software),
Err(StunWriteError::MessageIntegrityExists)
));
}
}
#[test]
fn add_raw_attribute_after_integrity() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, algorithm).unwrap();
let software = Software::new("s").unwrap();
let raw = software.to_raw();
assert!(matches!(
msg.add_attribute(&raw),
Err(StunWriteError::MessageIntegrityExists)
));
}
}
#[test]
fn add_integrity_after_fingerprint() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
assert!(matches!(
msg.add_message_integrity(&credentials, algorithm),
Err(StunWriteError::FingerprintExists)
));
}
}
#[test]
fn duplicate_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
assert!(matches!(
msg.add_fingerprint(),
Err(StunWriteError::AttributeExists(Fingerprint::TYPE))
));
}
#[test]
fn parse_invalid_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
bytes[24] = 0x80;
bytes[25] = 0x80;
bytes[26] = 0x80;
bytes[27] = 0x80;
assert!(matches!(
Message::from_bytes(&bytes),
Err(StunParseError::FingerprintMismatch)
));
}
#[test]
fn parse_wrong_magic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
bytes[4] = 0x80;
assert!(matches!(
Message::from_bytes(&bytes),
Err(StunParseError::NotStun)
));
}
#[test]
fn parse_attribute_after_integrity() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, algorithm).unwrap();
let mut bytes = msg.finish();
let software = Software::new("s").unwrap();
let software_bytes = RawAttribute::from(&software).to_bytes();
let software_len = software_bytes.len();
bytes.extend(software_bytes);
bytes[3] += software_len as u8;
let msg = Message::from_bytes(&bytes).unwrap();
assert!(msg.raw_attribute(Software::TYPE).is_none());
}
}
#[test]
fn parse_duplicate_integrity_after_integrity() {
let _log = crate::tests::test_init_log();
for algorithm in [IntegrityAlgorithm::Sha1, IntegrityAlgorithm::Sha256] {
let credentials = ShortTermCredentials::new("secret".to_owned()).into();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_message_integrity(&credentials, algorithm).unwrap();
add_message_integrity_unchecked(
&mut msg,
&credentials.make_key(IntegrityAlgorithm::Sha1),
algorithm,
);
let bytes = msg.finish();
let integrity_type = match algorithm {
IntegrityAlgorithm::Sha1 => MessageIntegrity::TYPE,
IntegrityAlgorithm::Sha256 => MessageIntegritySha256::TYPE,
};
let msg = Message::from_bytes(&bytes).unwrap();
msg.nth_raw_attribute(integrity_type, 0).unwrap();
assert!(msg.nth_raw_attribute(integrity_type, 1).is_none());
}
}
#[test]
fn parse_attribute_after_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
let software = Software::new("s").unwrap();
let software_bytes = RawAttribute::from(&software).to_bytes();
let software_len = software_bytes.len();
bytes.extend(software_bytes);
bytes[3] += software_len as u8;
let msg = Message::from_bytes(&bytes).unwrap();
assert!(msg.raw_attribute(Fingerprint::TYPE).is_some());
assert!(msg.raw_attribute(Software::TYPE).is_none());
}
#[test]
fn parse_duplicate_fingerprint_after_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
add_fingerprint_unchecked(&mut msg);
let bytes = msg.finish();
let msg = Message::from_bytes(&bytes).unwrap();
assert!(msg.nth_raw_attribute(Fingerprint::TYPE, 0).is_some());
assert!(msg.nth_raw_attribute(Fingerprint::TYPE, 1).is_none());
}
#[test]
fn add_attribute_after_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let software = Software::new("s").unwrap();
assert!(matches!(
msg.add_attribute(&software),
Err(StunWriteError::FingerprintExists)
));
}
#[test]
fn add_raw_attribute_after_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let software = Software::new("s").unwrap();
let raw = software.to_raw();
assert!(matches!(
msg.add_attribute(&raw),
Err(StunWriteError::FingerprintExists)
));
}
#[test]
fn parse_truncated_message_header() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let bytes = msg.finish();
assert!(matches!(
Message::from_bytes(&bytes[..8]),
Err(StunParseError::Truncated {
expected: 20,
actual: 8
})
));
}
#[test]
fn parse_truncated_message() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let bytes = msg.finish();
assert!(matches!(
Message::from_bytes(&bytes[..24]),
Err(StunParseError::Truncated {
expected: 28,
actual: 24
})
));
}
#[test]
fn parse_truncated_message_attribute() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
bytes[3] = 4;
assert!(matches!(
Message::from_bytes(&bytes[..24]),
Err(StunParseError::Truncated {
expected: 28,
actual: 24
})
));
}
#[test]
fn parse_attribute_extends_past_message_end() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_attribute(&Software::new("a").unwrap()).unwrap();
msg[23] += 1;
let mut bytes = msg.finish();
bytes[3] -= 1;
error!("{bytes:x?}");
error!("{:?}", Message::from_bytes(&bytes[..27]));
assert!(matches!(
Message::from_bytes(&bytes[..27]),
Err(StunParseError::Truncated {
expected: 28,
actual: 27
})
));
}
#[test]
fn valid_attributes() {
let _log = crate::tests::test_init_log();
let mut src = Message::builder_request(BINDING, MessageWriteVec::new());
let username = Username::new("123").unwrap();
src.add_attribute(&username).unwrap();
let nonce = Nonce::new("nonce").unwrap();
src.add_attribute(&nonce).unwrap();
assert!(!src.has_attribute(Fingerprint::TYPE));
assert!(src.has_attribute(Nonce::TYPE));
let src = src.finish();
let src = Message::from_bytes(&src).unwrap();
assert!(!src.has_attribute(Fingerprint::TYPE));
assert!(src.has_attribute(Nonce::TYPE));
let res = Message::check_attribute_types(
&src,
&[Username::TYPE, Nonce::TYPE],
&[Username::TYPE],
MessageWriteVec::new(),
);
assert!(res.is_none());
let res = Message::check_attribute_types(
&src,
&[Username::TYPE, Nonce::TYPE],
&[Fingerprint::TYPE],
MessageWriteVec::new(),
);
assert!(res.is_some());
let res = res.unwrap();
let res = res.finish();
let res = Message::from_bytes(&res).unwrap();
assert!(res.has_class(MessageClass::Error));
assert!(res.has_method(src.method()));
let err = res.attribute::<ErrorCode>().unwrap();
assert_eq!(err.code(), 400);
let res =
Message::check_attribute_types(&src, &[Username::TYPE], &[], MessageWriteVec::new());
assert!(res.is_some());
let res = res.unwrap();
let data = res.finish();
let res = Message::from_bytes(&data).unwrap();
assert!(res.has_class(MessageClass::Error));
assert!(res.has_method(src.method()));
let err = res.attribute::<ErrorCode>().unwrap();
assert_eq!(err.code(), 420);
let unknown = res.attribute::<UnknownAttributes>().unwrap();
assert!(unknown.has_attribute(Nonce::TYPE));
}
#[test]
fn attributes_iter_with_short_data() {
let _log = crate::tests::test_init_log();
assert_eq!(
MessageAttributesIter::new(&[0x0, 0x1, 0x2, 0x3, 0x4]).next(),
None
);
assert_eq!(
MessageAttributesIter::new(&[
0x0, 0x1, 0x2, 0x3, 0x21, 0x12, 0xa4, 0x42, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
0x10, 0x11, 0x12, 0x13, 0x14,
])
.next(),
None
);
}
#[test]
fn attributes_iter_software_after_fingerprint_ignored() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
let software = Software::new("s").unwrap();
let software_bytes = RawAttribute::from(&software).to_bytes();
let software_len = software_bytes.len();
bytes.extend(software_bytes);
bytes[3] += software_len as u8;
let mut it = MessageAttributesIter::new(&bytes);
let (_offset, fingerprint) = it.next().unwrap();
assert_eq!(fingerprint.get_type(), Fingerprint::TYPE);
assert_eq!(it.next(), None);
}
#[test]
fn attributes_iter_message_integrities_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let credentials = ShortTermCredentials::new("pass".to_owned());
msg.add_message_integrity(&credentials.clone().into(), IntegrityAlgorithm::Sha1)
.unwrap();
msg.add_message_integrity(&credentials.clone().into(), IntegrityAlgorithm::Sha256)
.unwrap();
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
let software = Software::new("s").unwrap();
let software_bytes = RawAttribute::from(&software).to_bytes();
let software_len = software_bytes.len();
bytes.extend(software_bytes);
bytes[3] += software_len as u8;
let mut it = MessageAttributesIter::new(&bytes);
assert_eq!(it.next().unwrap().1.get_type(), MessageIntegrity::TYPE);
assert_eq!(
it.next().unwrap().1.get_type(),
MessageIntegritySha256::TYPE
);
assert_eq!(it.next().unwrap().1.get_type(), Fingerprint::TYPE);
assert_eq!(it.next(), None);
}
#[test]
fn message_parse_multiple_integrities_fingerprint() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let credentials = ShortTermCredentials::new("pass".to_owned());
msg.add_message_integrity(&credentials.clone().into(), IntegrityAlgorithm::Sha1)
.unwrap();
msg.add_message_integrity(&credentials.clone().into(), IntegrityAlgorithm::Sha256)
.unwrap();
msg.add_fingerprint().unwrap();
let key = credentials.make_key();
let bytes = msg.finish();
let msg = Message::from_bytes(&bytes).unwrap();
assert!(msg.has_attribute(MessageIntegrity::TYPE));
assert!(msg.has_attribute(MessageIntegritySha256::TYPE));
assert!(!msg.has_attribute(Software::TYPE));
assert_eq!(
msg.validate_integrity(&credentials.clone().into()).unwrap(),
IntegrityAlgorithm::Sha256
);
assert_eq!(
msg.validate_integrity_with_key(&key).unwrap(),
IntegrityAlgorithm::Sha256
);
}
#[test]
fn validate_integrity_missing_attribute() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let credentials = ShortTermCredentials::new("pass".to_owned());
let key = credentials.make_key();
let bytes = msg.finish();
let msg = Message::from_bytes(&bytes).unwrap();
assert!(matches!(
msg.validate_integrity(&credentials.clone().into()),
Err(ValidateError::Parse(StunParseError::MissingAttribute(
MessageIntegrity::TYPE
)))
));
assert!(matches!(
msg.validate_integrity_with_key(&key),
Err(ValidateError::Parse(StunParseError::MissingAttribute(
MessageIntegrity::TYPE
)))
));
}
#[test]
fn attributes_iter_fingerprint_after_fingerprint_ignored() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
msg.add_fingerprint().unwrap();
let mut bytes = msg.finish();
let fingerprint = Fingerprint::new([bytes[bytes.len() - 1].wrapping_sub(1); 4]);
let fingerprint_bytes = RawAttribute::from(&fingerprint).to_bytes();
let fingerprint_len = fingerprint_bytes.len();
bytes.extend(fingerprint_bytes);
bytes[3] += fingerprint_len as u8;
let mut it = MessageAttributesIter::new(&bytes);
let (_offset, fingerprint) = it.next().unwrap();
assert_eq!(fingerprint.get_type(), Fingerprint::TYPE);
assert_eq!(it.next(), None);
}
#[test]
fn write_vec_state() {
let _log = crate::tests::test_init_log();
let mut src = Message::builder_request(BINDING, MessageWriteVec::with_capacity(0x10));
assert_eq!(src.len(), 20);
assert_eq!(src[4], 0x21);
let username = Username::new("123").unwrap();
src.add_attribute(&username).unwrap();
let nonce = Nonce::new("nonce").unwrap();
src.add_attribute(&nonce).unwrap();
assert!(src.has_attribute(Username::TYPE));
assert!(src.has_attribute(Nonce::TYPE));
assert_eq!(
src.has_any_attribute(&[Username::TYPE, Nonce::TYPE]),
Some(Username::TYPE)
);
assert_eq!(
src.has_any_attribute(&[Nonce::TYPE, Username::TYPE]),
Some(Username::TYPE)
);
assert!(src.has_class(MessageClass::Request));
assert!(!src.is_response());
assert!(src.has_method(BINDING));
assert!(src.get_type().has_method(BINDING));
assert!(src.get_type().has_class(MessageClass::Request));
assert!(!src.has_method(Method::new(0x111)));
assert!(!src.get_type().has_method(Method::new(0x111)));
assert!(!src.get_type().has_class(MessageClass::Error));
}
#[test]
fn write_mut_slice_success() {
let _log = crate::tests::test_init_log();
let mut data = [0; 64];
let mut src = Message::builder_request(BINDING, MessageWriteMutSlice::new(&mut data));
assert_eq!(src.len(), 20);
assert_eq!(src[4], 0x21);
let username = Username::new("123").unwrap();
src.add_attribute(&username).unwrap();
let nonce = Nonce::new("nonce").unwrap();
src.add_attribute(&nonce).unwrap();
assert!(src.has_attribute(Username::TYPE));
assert_eq!(
src.has_any_attribute(&[Username::TYPE]),
Some(Username::TYPE)
);
assert!(!src.has_attribute(Software::TYPE));
assert_eq!(src.has_any_attribute(&[Realm::TYPE]), None);
assert_eq!(src.mut_data().len(), src.len());
assert_eq!(src.finish(), 40);
let msg = Message::from_bytes(&data[..40]).unwrap();
let u2 = msg.attribute::<Username>().unwrap();
assert_eq!(u2.username(), "123");
let n2 = msg.attribute::<Nonce>().unwrap();
assert_eq!(n2.nonce(), "nonce");
}
#[test]
fn write_mut_slice_too_short() {
let _log = crate::tests::test_init_log();
let mut data = [0; 27];
let mut src = Message::builder_request(BINDING, MessageWriteMutSlice::new(&mut data));
assert!(matches!(
src.add_attribute(&Username::new("123").unwrap()),
Err(StunWriteError::TooSmall {
expected: 28,
actual: 27
})
));
}
#[test]
#[should_panic(expected = "created from a non-request message")]
fn builder_success_panic() {
let _log = crate::tests::test_init_log();
let msg = Message::builder(
MessageType::from_class_method(MessageClass::Indication, BINDING),
TransactionId::generate(),
MessageWriteVec::new(),
)
.finish();
let msg = Message::from_bytes(&msg).unwrap();
let _builder = Message::builder_success(&msg, MessageWriteVec::new());
}
#[test]
#[should_panic(expected = "created from a non-request message")]
fn builder_error_panic() {
let _log = crate::tests::test_init_log();
let msg = Message::builder(
MessageType::from_class_method(MessageClass::Indication, BINDING),
TransactionId::generate(),
MessageWriteVec::new(),
)
.finish();
let msg = Message::from_bytes(&msg).unwrap();
let _builder = Message::builder_error(&msg, MessageWriteVec::new());
}
#[test]
#[should_panic(expected = "Use add_message_integrity() instead")]
fn builder_add_attribute_integrity_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let hmac = [2; 20];
let integrity = MessageIntegrity::new(hmac);
msg.add_attribute(&integrity).unwrap();
}
#[test]
#[should_panic(expected = "Use add_message_integrity() instead")]
fn builder_add_raw_attribute_integrity_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let hmac = [2; 20];
let integrity = MessageIntegrity::new(hmac);
let raw = integrity.to_raw();
msg.add_attribute(&raw).unwrap();
}
#[test]
#[should_panic(expected = "Use add_message_integrity() instead")]
fn builder_add_attribute_integrity_sha256_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let hmac = [2; 16];
let integrity = MessageIntegritySha256::new(&hmac).unwrap();
msg.add_attribute(&integrity).unwrap();
}
#[test]
#[should_panic(expected = "Use add_message_integrity() instead")]
fn builder_add_raw_attribute_integrity_sha256_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let hmac = [2; 16];
let integrity = MessageIntegritySha256::new(&hmac).unwrap();
let raw = integrity.to_raw();
msg.add_attribute(&raw).unwrap();
}
#[test]
#[should_panic(expected = "Use add_fingerprint() instead")]
fn builder_add_attribute_fingerprint_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let fingerprint = [2; 4];
let fingerprint = Fingerprint::new(fingerprint);
msg.add_attribute(&fingerprint).unwrap();
}
#[test]
#[should_panic(expected = "Use add_fingerprint() instead")]
fn builder_add_raw_attribute_fingerprint_panic() {
let _log = crate::tests::test_init_log();
let mut msg = Message::builder_request(BINDING, MessageWriteVec::new());
let fingerprint = [2; 4];
let fingerprint = Fingerprint::new(fingerprint);
let raw = fingerprint.to_raw();
msg.add_attribute(&raw).unwrap();
}
#[test]
fn rfc5769_vector1() {
let _log = crate::tests::test_init_log();
let data = vec![
0x00, 0x01, 0x00, 0x58, 0x21, 0x12, 0xa4, 0x42, 0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae, 0x80, 0x22, 0x00, 0x10, 0x53, 0x54, 0x55, 0x4e, 0x20, 0x74, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x00, 0x24, 0x00, 0x04, 0x6e, 0x00, 0x01, 0xff, 0x80, 0x29, 0x00, 0x08, 0x93, 0x2f, 0xf9, 0xb1, 0x51, 0x26, 0x3b, 0x36, 0x00, 0x06, 0x00, 0x09, 0x65, 0x76, 0x74, 0x6a, 0x3a, 0x68, 0x36, 0x76, 0x59, 0x20, 0x20, 0x20, 0x00, 0x08, 0x00, 0x14, 0x9a, 0xea, 0xa7, 0x0c, 0xbf, 0xd8, 0xcb, 0x56, 0x78, 0x1e, 0xf2, 0xb5, 0xb2, 0xd3, 0xf2, 0x49, 0xc1, 0xb5, 0x71, 0xa2, 0x80, 0x28, 0x00, 0x04, 0xe5, 0x7a, 0x3b, 0xcf, ];
let msg = Message::from_bytes(&data).unwrap();
assert!(msg.has_class(MessageClass::Request));
assert!(msg.has_method(BINDING));
assert_eq!(msg.transaction_id(), 0xb7e7_a701_bc34_d686_fa87_dfae.into());
let mut builder = Message::builder(
MessageType::from_class_method(MessageClass::Request, BINDING),
msg.transaction_id(),
MessageWriteVec::new(),
);
assert!(msg.has_attribute(Software::TYPE));
let raw = msg.raw_attribute(Software::TYPE).unwrap();
assert!(Software::try_from(&raw).is_ok());
let software = Software::try_from(&raw).unwrap();
assert_eq!(software.software(), "STUN test client");
builder.add_attribute(&software).unwrap();
builder
.add_attribute(&RawAttribute::new(0x24.into(), &[0x6e, 0x00, 0x01, 0xff]))
.unwrap();
builder
.add_attribute(&RawAttribute::new(
0x8029.into(),
&[0x93, 0x2f, 0xf9, 0xb1, 0x51, 0x26, 0x3b, 0x36],
))
.unwrap();
assert!(msg.has_attribute(Username::TYPE));
let raw = msg.raw_attribute(Username::TYPE).unwrap();
assert!(Username::try_from(&raw).is_ok());
let username = Username::try_from(&raw).unwrap();
assert_eq!(username.username(), "evtj:h6vY");
builder.add_attribute(&username).unwrap();
let credentials = MessageIntegrityCredentials::ShortTerm(ShortTermCredentials {
password: "VOkJxbRl1RmTxUk/WvJxBt".to_owned(),
});
assert!(matches!(
msg.validate_integrity(&credentials),
Ok(IntegrityAlgorithm::Sha1)
));
builder
.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1)
.unwrap();
assert!(msg.has_attribute(Fingerprint::TYPE));
builder.add_fingerprint().unwrap();
let mut msg_data = builder.finish();
msg_data[73] = 0x20;
msg_data[74] = 0x20;
msg_data[75] = 0x20;
assert_eq!(msg_data[..80], data[..80]);
}
#[test]
fn rfc5769_vector2() {
let _log = crate::tests::test_init_log();
let data = vec![
0x01, 0x01, 0x00, 0x3c, 0x21, 0x12, 0xa4, 0x42, 0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae, 0x80, 0x22, 0x00, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x00, 0x20, 0x00, 0x08, 0x00, 0x01, 0xa1, 0x47, 0xe1, 0x12, 0xa6, 0x43, 0x00, 0x08, 0x00, 0x14, 0x2b, 0x91, 0xf5, 0x99, 0xfd, 0x9e, 0x90, 0xc3, 0x8c, 0x74, 0x89, 0xf9, 0x2a, 0xf9, 0xba, 0x53, 0xf0, 0x6b, 0xe7, 0xd7, 0x80, 0x28, 0x00, 0x04, 0xc0, 0x7d, 0x4c, 0x96, ];
let msg = Message::from_bytes(&data).unwrap();
assert!(msg.has_class(MessageClass::Success));
assert!(msg.has_method(BINDING));
assert_eq!(msg.transaction_id(), 0xb7e7_a701_bc34_d686_fa87_dfae.into());
let mut builder = Message::builder(
MessageType::from_class_method(MessageClass::Success, BINDING),
msg.transaction_id(),
MessageWriteVec::new(),
);
assert!(msg.has_attribute(Software::TYPE));
let raw = msg.raw_attribute(Software::TYPE).unwrap();
assert!(Software::try_from(&raw).is_ok());
let software = Software::try_from(&raw).unwrap();
assert_eq!(software.software(), "test vector");
builder.add_attribute(&software).unwrap();
assert!(msg.has_attribute(XorMappedAddress::TYPE));
let raw = msg.raw_attribute(XorMappedAddress::TYPE).unwrap();
assert!(XorMappedAddress::try_from(&raw).is_ok());
let xor_mapped_addres = XorMappedAddress::try_from(&raw).unwrap();
assert_eq!(
xor_mapped_addres.addr(msg.transaction_id()),
"192.0.2.1:32853".parse().unwrap()
);
builder.add_attribute(&xor_mapped_addres).unwrap();
let credentials = MessageIntegrityCredentials::ShortTerm(ShortTermCredentials {
password: "VOkJxbRl1RmTxUk/WvJxBt".to_owned(),
});
let ret = msg.validate_integrity(&credentials);
warn!("{:?}", ret);
assert!(matches!(ret, Ok(IntegrityAlgorithm::Sha1)));
builder
.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1)
.unwrap();
assert!(msg.has_attribute(Fingerprint::TYPE));
builder.add_fingerprint().unwrap();
let mut msg_data = builder.finish();
msg_data[35] = 0x20;
assert_eq!(msg_data[..52], data[..52]);
}
#[test]
fn rfc5769_vector3() {
let _log = crate::tests::test_init_log();
let data = vec![
0x01, 0x01, 0x00, 0x48, 0x21, 0x12, 0xa4, 0x42, 0xb7, 0xe7, 0xa7, 0x01, 0xbc, 0x34, 0xd6, 0x86, 0xfa, 0x87, 0xdf, 0xae, 0x80, 0x22, 0x00, 0x0b, 0x74, 0x65, 0x73, 0x74, 0x20, 0x76, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x20, 0x00, 0x20, 0x00, 0x14, 0x00, 0x02, 0xa1, 0x47, 0x01, 0x13, 0xa9, 0xfa, 0xa5, 0xd3, 0xf1, 0x79, 0xbc, 0x25, 0xf4, 0xb5, 0xbe, 0xd2, 0xb9, 0xd9, 0x00, 0x08, 0x00, 0x14, 0xa3, 0x82, 0x95, 0x4e, 0x4b, 0xe6, 0x7b, 0xf1, 0x17, 0x84, 0xc9, 0x7c, 0x82, 0x92, 0xc2, 0x75, 0xbf, 0xe3, 0xed, 0x41, 0x80, 0x28, 0x00, 0x04, 0xc8, 0xfb, 0x0b, 0x4c, ];
let msg = Message::from_bytes(&data).unwrap();
assert!(msg.has_class(MessageClass::Success));
assert!(msg.has_method(BINDING));
assert_eq!(msg.transaction_id(), 0xb7e7_a701_bc34_d686_fa87_dfae.into());
let mut builder = Message::builder(
MessageType::from_class_method(MessageClass::Success, BINDING),
msg.transaction_id(),
MessageWriteVec::new(),
);
assert!(msg.has_attribute(Software::TYPE));
let raw = msg.raw_attribute(Software::TYPE).unwrap();
assert!(Software::try_from(&raw).is_ok());
let software = Software::try_from(&raw).unwrap();
assert_eq!(software.software(), "test vector");
builder.add_attribute(&software).unwrap();
assert!(msg.has_attribute(XorMappedAddress::TYPE));
let raw = msg.raw_attribute(XorMappedAddress::TYPE).unwrap();
assert!(XorMappedAddress::try_from(&raw).is_ok());
let xor_mapped_addres = XorMappedAddress::try_from(&raw).unwrap();
assert_eq!(
xor_mapped_addres.addr(msg.transaction_id()),
"[2001:db8:1234:5678:11:2233:4455:6677]:32853"
.parse()
.unwrap()
);
builder.add_attribute(&xor_mapped_addres).unwrap();
let credentials = MessageIntegrityCredentials::ShortTerm(ShortTermCredentials {
password: "VOkJxbRl1RmTxUk/WvJxBt".to_owned(),
});
assert!(matches!(
msg.validate_integrity(&credentials),
Ok(IntegrityAlgorithm::Sha1)
));
builder
.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1)
.unwrap();
assert!(msg.has_attribute(Fingerprint::TYPE));
builder.add_fingerprint().unwrap();
let mut msg_data = builder.finish();
msg_data[35] = 0x20;
assert_eq!(msg_data[..64], data[..64]);
}
#[test]
fn rfc5769_vector4() {
let _log = crate::tests::test_init_log();
let data = vec![
0x00, 0x01, 0x00, 0x60, 0x21, 0x12, 0xa4, 0x42, 0x78, 0xad, 0x34, 0x33, 0xc6, 0xad, 0x72, 0xc0, 0x29, 0xda, 0x41, 0x2e, 0x00, 0x06, 0x00, 0x12, 0xe3, 0x83, 0x9e, 0xe3, 0x83, 0x88, 0xe3, 0x83, 0xaa, 0xe3, 0x83, 0x83, 0xe3, 0x82, 0xaf, 0xe3, 0x82, 0xb9, 0x00, 0x00, 0x00, 0x15, 0x00, 0x1c, 0x66, 0x2f, 0x2f, 0x34, 0x39, 0x39, 0x6b, 0x39, 0x35, 0x34, 0x64, 0x36, 0x4f, 0x4c, 0x33, 0x34, 0x6f, 0x4c, 0x39, 0x46, 0x53, 0x54, 0x76, 0x79, 0x36, 0x34, 0x73, 0x41, 0x00, 0x14, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x08, 0x00, 0x14, 0xf6, 0x70, 0x24, 0x65, 0x6d, 0xd6, 0x4a, 0x3e, 0x02, 0xb8, 0xe0, 0x71, 0x2e, 0x85, 0xc9, 0xa2, 0x8c, 0xa8, 0x96, 0x66, ];
let msg = Message::from_bytes(&data).unwrap();
assert!(msg.has_class(MessageClass::Request));
assert!(msg.has_method(BINDING));
assert_eq!(msg.transaction_id(), 0x78ad_3433_c6ad_72c0_29da_412e.into());
let mut builder = Message::builder(
MessageType::from_class_method(MessageClass::Request, BINDING),
msg.transaction_id(),
MessageWriteVec::new(),
);
let username = stringprep::saslprep("\u{30DE}\u{30C8}\u{30EA}\u{30C3}\u{30AF}\u{30B9}")
.unwrap()
.into_owned();
let password = stringprep::saslprep("The\u{00AD}M\u{00AA}tr\u{2168}")
.unwrap()
.into_owned();
trace!("password: {password:?}");
let long_term = LongTermKeyCredentials {
username,
password,
realm: "example.org".to_owned(),
};
assert!(msg.has_attribute(Username::TYPE));
let raw = msg.raw_attribute(Username::TYPE).unwrap();
assert!(Username::try_from(&raw).is_ok());
let username = Username::try_from(&raw).unwrap();
assert_eq!(username.username(), &long_term.username);
builder.add_attribute(&username).unwrap();
let expected_nonce = "f//499k954d6OL34oL9FSTvy64sA";
assert!(msg.has_attribute(Nonce::TYPE));
let raw = msg.raw_attribute(Nonce::TYPE).unwrap();
assert!(Nonce::try_from(&raw).is_ok());
let nonce = Nonce::try_from(&raw).unwrap();
assert_eq!(nonce.nonce(), expected_nonce);
builder.add_attribute(&nonce).unwrap();
assert!(msg.has_attribute(Realm::TYPE));
let raw = msg.raw_attribute(Realm::TYPE).unwrap();
assert!(Realm::try_from(&raw).is_ok());
let realm = Realm::try_from(&raw).unwrap();
assert_eq!(realm.realm(), long_term.realm());
builder.add_attribute(&realm).unwrap();
let credentials = MessageIntegrityCredentials::LongTerm(long_term);
assert!(matches!(
msg.validate_integrity(&credentials),
Ok(IntegrityAlgorithm::Sha1)
));
builder
.add_message_integrity(&credentials, IntegrityAlgorithm::Sha1)
.unwrap();
assert_eq!(builder.finish()[4..], data[4..]);
}
#[test]
fn rfc8489_vector1() {
let _log = crate::tests::test_init_log();
let data = vec![
0x00, 0x01, 0x00, 0x90, 0x21, 0x12, 0xa4, 0x42, 0x78, 0xad, 0x34, 0x33, 0xc6, 0xad, 0x72, 0xc0, 0x29, 0xda, 0x41, 0x2e, 0x00, 0x1e, 0x00, 0x20, 0x4a, 0x3c, 0xf3, 0x8f, 0xef, 0x69, 0x92, 0xbd, 0xa9, 0x52, 0xc6, 0x78, 0x04, 0x17, 0xda, 0x0f, 0x24, 0x81, 0x94, 0x15, 0x56, 0x9e, 0x60, 0xb2, 0x05, 0xc4, 0x6e, 0x41, 0x40, 0x7f, 0x17, 0x04, 0x00, 0x15, 0x00, 0x29, 0x6f, 0x62, 0x4d, 0x61, 0x74, 0x4a, 0x6f, 0x73, 0x32, 0x41, 0x41, 0x41, 0x43, 0x66, 0x2f, 0x2f, 0x34, 0x39, 0x39, 0x6b, 0x39, 0x35, 0x34, 0x64, 0x36, 0x4f, 0x4c, 0x33, 0x34, 0x6f, 0x4c, 0x39, 0x46, 0x53, 0x54, 0x76, 0x79, 0x36, 0x34, 0x73, 0x41, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x0b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x1d, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x20, 0xb5, 0xc7, 0xbf, 0x00, 0x5b, 0x6c, 0x52, 0xa2, 0x1c, 0x51, 0xc5, 0xe8, 0x92, 0xf8, 0x19, 0x24, 0x13, 0x62, 0x96, 0xcb, 0x92, 0x7c, 0x43, 0x14, 0x93, 0x09, 0x27, 0x8c, 0xc6, 0x51, 0x8e, 0x65, ];
let msg = Message::from_bytes(&data).unwrap();
assert!(msg.has_class(MessageClass::Request));
assert!(msg.has_method(BINDING));
assert_eq!(msg.transaction_id(), 0x78ad_3433_c6ad_72c0_29da_412e.into());
let mut builder = Message::builder(
MessageType::from_class_method(MessageClass::Request, BINDING),
msg.transaction_id(),
MessageWriteVec::new(),
);
let opaque = precis_profiles::OpaqueString::new();
let username = opaque
.prepare("\u{30DE}\u{30C8}\u{30EA}\u{30C3}\u{30AF}\u{30B9}")
.unwrap()
.into_owned();
let orig_password = "The\u{00AD}M\u{00AA}tr\u{2168}";
trace!("password: {orig_password:?}");
let sasl_password = stringprep::saslprep(orig_password).unwrap().into_owned();
trace!("password (saslprep): {sasl_password}");
let long_term = LongTermKeyCredentials {
username: username.clone(),
password: sasl_password.clone(),
realm: "example.org".to_owned(),
};
assert!(msg.has_attribute(Userhash::TYPE));
let raw = msg.raw_attribute(Userhash::TYPE).unwrap();
assert!(Userhash::try_from(&raw).is_ok());
let userhash = Userhash::try_from(&raw).unwrap();
builder.add_attribute(&userhash).unwrap();
let expected_nonce = "obMatJos2AAACf//499k954d6OL34oL9FSTvy64sA";
assert!(msg.has_attribute(Nonce::TYPE));
let raw = msg.raw_attribute(Nonce::TYPE).unwrap();
assert!(Nonce::try_from(&raw).is_ok());
let nonce = Nonce::try_from(&raw).unwrap();
assert_eq!(nonce.nonce(), expected_nonce);
builder.add_attribute(&nonce).unwrap();
assert!(msg.has_attribute(Realm::TYPE));
let raw = msg.raw_attribute(Realm::TYPE).unwrap();
assert!(Realm::try_from(&raw).is_ok());
let realm = Realm::try_from(&raw).unwrap();
assert_eq!(realm.realm(), long_term.realm);
builder.add_attribute(&realm).unwrap();
assert!(msg.has_attribute(PasswordAlgorithm::TYPE));
let raw = msg.raw_attribute(PasswordAlgorithm::TYPE).unwrap();
assert!(PasswordAlgorithm::try_from(&raw).is_ok());
let algo = PasswordAlgorithm::try_from(&raw).unwrap();
assert_eq!(algo.algorithm(), PasswordAlgorithmValue::SHA256);
builder.add_attribute(&algo).unwrap();
trace!("long term: {long_term:?}");
let credentials = MessageIntegrityCredentials::LongTerm(long_term);
assert!(matches!(
msg.validate_integrity(&credentials),
Ok(IntegrityAlgorithm::Sha256)
));
builder
.add_message_integrity(&credentials, IntegrityAlgorithm::Sha256)
.unwrap();
assert_eq!(builder.finish()[4..], data[4..]);
}
}