use bytes::{Buf, BufMut, Bytes, BytesMut};
use hmac::{Hmac, Mac};
use rand::RngCore;
use sha1::Sha1;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;
use thiserror::Error;
use tokio::net::UdpSocket;
use tokio::time::timeout;
use tracing::{debug, trace};
#[cfg(test)]
use std::sync::atomic::{AtomicU64, Ordering};
type HmacSha1 = Hmac<Sha1>;
const MAGIC_COOKIE: u32 = 0x2112A442;
const ALLOCATE_REQUEST: u16 = 0x0003;
const ALLOCATE_RESPONSE: u16 = 0x0103;
const ALLOCATE_ERROR: u16 = 0x0113;
const REFRESH_REQUEST: u16 = 0x0004;
const REFRESH_RESPONSE: u16 = 0x0104;
const SEND_INDICATION: u16 = 0x0016;
const DATA_INDICATION: u16 = 0x0017;
const CREATE_PERMISSION_REQUEST: u16 = 0x0008;
const CREATE_PERMISSION_RESPONSE: u16 = 0x0108;
#[allow(dead_code)]
const CHANNEL_BIND_REQUEST: u16 = 0x0009;
#[allow(dead_code)]
const CHANNEL_BIND_RESPONSE: u16 = 0x0109;
const ATTR_XOR_MAPPED_ADDRESS: u16 = 0x0020;
const ATTR_XOR_RELAYED_ADDRESS: u16 = 0x0016;
const ATTR_XOR_PEER_ADDRESS: u16 = 0x0012;
const ATTR_LIFETIME: u16 = 0x000D;
const ATTR_DATA: u16 = 0x0013;
const ATTR_REQUESTED_TRANSPORT: u16 = 0x0019;
const ATTR_USERNAME: u16 = 0x0006;
const ATTR_REALM: u16 = 0x0014;
const ATTR_NONCE: u16 = 0x0015;
const ATTR_MESSAGE_INTEGRITY: u16 = 0x0008;
const ATTR_ERROR_CODE: u16 = 0x0009;
#[allow(dead_code)]
const ATTR_CHANNEL_NUMBER: u16 = 0x000C;
const TRANSPORT_UDP: u8 = 17;
const AF_IPV4: u8 = 0x01;
const AF_IPV6: u8 = 0x02;
#[derive(Error, Debug)]
pub enum TurnError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Request timeout")]
Timeout,
#[error("Invalid response: {0}")]
InvalidResponse(String),
#[error("TURN error: {code} {reason}")]
ErrorResponse {
code: u16,
reason: String,
},
#[error("Authentication required")]
AuthRequired {
realm: String,
nonce: String,
},
#[error("No relay address in response")]
NoRelayAddress,
#[error("Allocation not active")]
NotAllocated,
}
#[cfg(test)]
static FORCE_BIND_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
static FORCE_CONNECT_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
static FORCE_LOCAL_ADDR_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
static FORCE_SEND_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
static FORCE_RECV_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
static FORCE_AUTH_SEND_ERROR: AtomicU64 = AtomicU64::new(0);
#[cfg(test)]
fn force_bind_error_once() {
FORCE_BIND_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn force_connect_error_once() {
FORCE_CONNECT_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn force_local_addr_error_once() {
FORCE_LOCAL_ADDR_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn force_send_error_once() {
FORCE_SEND_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn force_recv_error_once() {
FORCE_RECV_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn force_auth_send_error_once() {
FORCE_AUTH_SEND_ERROR.store(current_thread_id(), Ordering::SeqCst);
}
#[cfg(test)]
fn take_forced_error(flag: &AtomicU64, message: &str) -> Option<std::io::Error> {
let current = current_thread_id();
if flag.load(Ordering::SeqCst) == current {
let _ = flag.compare_exchange(current, 0, Ordering::SeqCst, Ordering::SeqCst);
Some(std::io::Error::other(message))
} else {
None
}
}
#[cfg(test)]
fn current_thread_id() -> u64 {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
std::thread::current().id().hash(&mut hasher);
let id = hasher.finish();
normalize_thread_id(id)
}
#[cfg(test)]
fn normalize_thread_id(id: u64) -> u64 {
if id == 0 {
1
} else {
id
}
}
async fn bind_socket() -> Result<UdpSocket, TurnError> {
let socket = socket_bind("0.0.0.0:0").await.map_err(TurnError::Io)?;
Ok(socket)
}
async fn socket_bind(addr: &str) -> Result<UdpSocket, std::io::Error> {
#[cfg(test)]
if let Some(err) = take_forced_error(&FORCE_BIND_ERROR, "forced bind error") {
return Err(err);
}
UdpSocket::bind(addr).await
}
async fn connect_socket(socket: &UdpSocket, addr: SocketAddr) -> Result<(), TurnError> {
socket_connect(socket, addr).await.map_err(TurnError::Io)?;
Ok(())
}
async fn socket_connect(socket: &UdpSocket, addr: SocketAddr) -> Result<(), std::io::Error> {
#[cfg(test)]
if let Some(err) = take_forced_error(&FORCE_CONNECT_ERROR, "forced connect error") {
return Err(err);
}
socket.connect(&addr).await
}
fn socket_local_addr(socket: &UdpSocket) -> Result<SocketAddr, TurnError> {
socket_local_addr_inner(socket).map_err(TurnError::Io)
}
fn socket_local_addr_inner(socket: &UdpSocket) -> Result<SocketAddr, std::io::Error> {
#[cfg(test)]
if let Some(err) = take_forced_error(&FORCE_LOCAL_ADDR_ERROR, "forced local_addr error") {
return Err(err);
}
socket.local_addr()
}
async fn socket_send(socket: &UdpSocket, data: &[u8]) -> Result<(), TurnError> {
socket_send_inner(socket, data)
.await
.map_err(TurnError::Io)?;
Ok(())
}
async fn socket_send_inner(socket: &UdpSocket, data: &[u8]) -> Result<usize, std::io::Error> {
#[cfg(test)]
if let Some(err) = take_forced_error(&FORCE_SEND_ERROR, "forced send error") {
return Err(err);
}
socket.send(data).await
}
async fn socket_recv(socket: &UdpSocket, buf: &mut [u8]) -> Result<usize, TurnError> {
socket_recv_inner(socket, buf).await.map_err(TurnError::Io)
}
async fn socket_recv_inner(socket: &UdpSocket, buf: &mut [u8]) -> Result<usize, std::io::Error> {
#[cfg(test)]
if let Some(err) = take_forced_error(&FORCE_RECV_ERROR, "forced recv error") {
return Err(err);
}
socket.recv(buf).await
}
#[derive(Debug, Clone)]
pub struct TurnServer {
pub addr: SocketAddr,
pub username: String,
pub password: String,
pub realm: Option<String>,
}
impl TurnServer {
pub fn new(addr: SocketAddr, username: &str, password: &str) -> Self {
Self {
addr,
username: username.to_string(),
password: password.to_string(),
realm: None,
}
}
}
#[derive(Debug, Clone)]
pub struct TurnAllocation {
pub relayed_addr: SocketAddr,
pub mapped_addr: SocketAddr,
pub lifetime: u32,
pub realm: String,
pub nonce: String,
}
pub struct TurnClient {
socket: UdpSocket,
server: TurnServer,
timeout: Duration,
retries: u32,
allocation: Option<TurnAllocation>,
transaction_id: [u8; 12],
}
impl TurnClient {
pub async fn new(server: TurnServer) -> Result<Self, TurnError> {
let socket = bind_socket().await?;
connect_socket(&socket, server.addr).await?;
debug!(
"TURN client bound to {}, connecting to {}",
socket_local_addr(&socket)?,
server.addr
);
Ok(Self {
socket,
server,
timeout: Duration::from_secs(5),
retries: 3,
allocation: None,
transaction_id: generate_transaction_id(),
})
}
pub fn local_addr(&self) -> Result<SocketAddr, TurnError> {
socket_local_addr(&self.socket)
}
pub fn allocation(&self) -> Option<&TurnAllocation> {
self.allocation.as_ref()
}
pub fn relayed_addr(&self) -> Option<SocketAddr> {
self.allocation.as_ref().map(|a| a.relayed_addr)
}
pub async fn allocate(&mut self) -> Result<TurnAllocation, TurnError> {
debug!("Requesting TURN allocation from {}", self.server.addr);
self.transaction_id = generate_transaction_id();
let request = self.build_allocate_request(None);
match self.send_request(&request).await? {
AllocateResult::Success(alloc) => {
self.allocation = Some(alloc.clone());
Ok(alloc)
}
AllocateResult::AuthRequired { realm, nonce } => {
debug!("Authentication required, realm={}, nonce={}", realm, nonce);
self.transaction_id = generate_transaction_id();
let auth = AuthContext {
username: self.server.username.clone(),
password: self.server.password.clone(),
realm: realm.clone(),
nonce: nonce.clone(),
};
let request = self.build_allocate_request(Some(&auth));
#[cfg(test)]
if let Some(err) =
take_forced_error(&FORCE_AUTH_SEND_ERROR, "forced auth send error")
{
return Err(TurnError::Io(err));
}
match self.send_request(&request).await? {
AllocateResult::Success(mut alloc) => {
alloc.realm = realm;
alloc.nonce = nonce;
self.allocation = Some(alloc.clone());
Ok(alloc)
}
AllocateResult::AuthRequired { .. } => Err(TurnError::ErrorResponse {
code: 401,
reason: "Authentication failed".into(),
}),
}
}
}
}
pub async fn refresh(&mut self, lifetime: u32) -> Result<u32, TurnError> {
let alloc = self.allocation.as_ref().ok_or(TurnError::NotAllocated)?;
debug!(
"Refreshing TURN allocation, requested lifetime={}",
lifetime
);
let auth = AuthContext {
username: self.server.username.clone(),
password: self.server.password.clone(),
realm: alloc.realm.clone(),
nonce: alloc.nonce.clone(),
};
self.transaction_id = generate_transaction_id();
let request = self.build_refresh_request(lifetime, &auth);
let response = self.send_raw(&request).await?;
let new_lifetime = self.parse_refresh_response(&response)?;
let alloc = self.allocation.as_mut().expect("allocation");
alloc.lifetime = new_lifetime;
Ok(new_lifetime)
}
pub async fn create_permission(&mut self, peer_addr: SocketAddr) -> Result<(), TurnError> {
let alloc = self.allocation.as_ref().ok_or(TurnError::NotAllocated)?;
debug!("Creating permission for peer {}", peer_addr);
let auth = AuthContext {
username: self.server.username.clone(),
password: self.server.password.clone(),
realm: alloc.realm.clone(),
nonce: alloc.nonce.clone(),
};
self.transaction_id = generate_transaction_id();
let request = self.build_permission_request(peer_addr, &auth);
let response = self.send_raw(&request).await?;
self.parse_permission_response(&response)?;
Ok(())
}
pub async fn send_data(&self, peer_addr: SocketAddr, data: &[u8]) -> Result<(), TurnError> {
if self.allocation.is_none() {
return Err(TurnError::NotAllocated);
}
trace!(
"Sending {} bytes to peer {} via relay",
data.len(),
peer_addr
);
let indication = self.build_send_indication(peer_addr, data);
socket_send(&self.socket, &indication).await?;
Ok(())
}
pub async fn recv_data(&self) -> Result<(SocketAddr, Vec<u8>), TurnError> {
let mut buf = vec![0u8; 65536];
let len = socket_recv(&self.socket, &mut buf).await?;
self.parse_data_indication(&buf[..len])
}
pub async fn recv_data_timeout(
&self,
duration: Duration,
) -> Result<(SocketAddr, Vec<u8>), TurnError> {
match timeout(duration, self.recv_data()).await {
Ok(result) => result,
Err(_) => Err(TurnError::Timeout),
}
}
fn build_allocate_request(&self, auth: Option<&AuthContext>) -> Bytes {
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_REQUESTED_TRANSPORT);
attrs.put_u16(4);
attrs.put_u8(TRANSPORT_UDP);
attrs.put_u8(0); attrs.put_u8(0);
attrs.put_u8(0);
if let Some(auth) = auth {
let username_bytes = auth.username.as_bytes();
attrs.put_u16(ATTR_USERNAME);
attrs.put_u16(username_bytes.len() as u16);
attrs.put_slice(username_bytes);
pad_to_4_bytes(&mut attrs, username_bytes.len());
let realm_bytes = auth.realm.as_bytes();
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm_bytes.len() as u16);
attrs.put_slice(realm_bytes);
pad_to_4_bytes(&mut attrs, realm_bytes.len());
let nonce_bytes = auth.nonce.as_bytes();
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce_bytes.len() as u16);
attrs.put_slice(nonce_bytes);
pad_to_4_bytes(&mut attrs, nonce_bytes.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len() + 24);
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&self.transaction_id);
msg.put_slice(&attrs);
add_message_integrity(&mut msg, &auth.username, &auth.realm, &self.server.password);
return msg.freeze();
}
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&self.transaction_id);
msg.put_slice(&attrs);
msg.freeze()
}
fn build_refresh_request(&self, lifetime: u32, auth: &AuthContext) -> Bytes {
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let username_bytes = auth.username.as_bytes();
attrs.put_u16(ATTR_USERNAME);
attrs.put_u16(username_bytes.len() as u16);
attrs.put_slice(username_bytes);
pad_to_4_bytes(&mut attrs, username_bytes.len());
let realm_bytes = auth.realm.as_bytes();
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm_bytes.len() as u16);
attrs.put_slice(realm_bytes);
pad_to_4_bytes(&mut attrs, realm_bytes.len());
let nonce_bytes = auth.nonce.as_bytes();
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce_bytes.len() as u16);
attrs.put_slice(nonce_bytes);
pad_to_4_bytes(&mut attrs, nonce_bytes.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len() + 24);
msg.put_u16(REFRESH_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&self.transaction_id);
msg.put_slice(&attrs);
add_message_integrity(&mut msg, &auth.username, &auth.realm, &self.server.password);
msg.freeze()
}
fn build_permission_request(&self, peer_addr: SocketAddr, auth: &AuthContext) -> Bytes {
let mut attrs = BytesMut::new();
encode_xor_address(
&mut attrs,
ATTR_XOR_PEER_ADDRESS,
peer_addr,
&self.transaction_id,
);
let username_bytes = auth.username.as_bytes();
attrs.put_u16(ATTR_USERNAME);
attrs.put_u16(username_bytes.len() as u16);
attrs.put_slice(username_bytes);
pad_to_4_bytes(&mut attrs, username_bytes.len());
let realm_bytes = auth.realm.as_bytes();
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm_bytes.len() as u16);
attrs.put_slice(realm_bytes);
pad_to_4_bytes(&mut attrs, realm_bytes.len());
let nonce_bytes = auth.nonce.as_bytes();
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce_bytes.len() as u16);
attrs.put_slice(nonce_bytes);
pad_to_4_bytes(&mut attrs, nonce_bytes.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len() + 24);
msg.put_u16(CREATE_PERMISSION_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&self.transaction_id);
msg.put_slice(&attrs);
add_message_integrity(&mut msg, &auth.username, &auth.realm, &self.server.password);
msg.freeze()
}
fn build_send_indication(&self, peer_addr: SocketAddr, data: &[u8]) -> Bytes {
let mut attrs = BytesMut::new();
encode_xor_address(
&mut attrs,
ATTR_XOR_PEER_ADDRESS,
peer_addr,
&self.transaction_id,
);
attrs.put_u16(ATTR_DATA);
attrs.put_u16(data.len() as u16);
attrs.put_slice(data);
pad_to_4_bytes(&mut attrs, data.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(SEND_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&self.transaction_id);
msg.put_slice(&attrs);
msg.freeze()
}
async fn send_request(&mut self, request: &[u8]) -> Result<AllocateResult, TurnError> {
let response = self.send_raw(request).await?;
self.parse_allocate_response(&response)
}
async fn send_raw(&self, request: &[u8]) -> Result<Vec<u8>, TurnError> {
for attempt in 0..self.retries {
if attempt > 0 {
debug!("Retry {} for TURN request", attempt);
}
socket_send(&self.socket, request).await?;
let mut buf = vec![0u8; 4096];
match timeout(self.timeout, socket_recv(&self.socket, &mut buf)).await {
Ok(result) => {
let len = result?;
trace!("Received {} bytes from TURN server", len);
return Ok(buf[..len].to_vec());
}
Err(_) => {
if attempt == self.retries - 1 {
return Err(TurnError::Timeout);
}
}
}
}
Err(TurnError::Timeout)
}
fn parse_allocate_response(&self, data: &[u8]) -> Result<AllocateResult, TurnError> {
if data.len() < 20 {
return Err(TurnError::InvalidResponse("Message too short".into()));
}
let mut buf = data;
let msg_type = buf.get_u16();
let msg_len = buf.get_u16() as usize;
let _cookie = buf.get_u32();
let mut txn_id = [0u8; 12];
buf.copy_to_slice(&mut txn_id);
if txn_id != self.transaction_id {
return Err(TurnError::InvalidResponse("Transaction ID mismatch".into()));
}
if msg_type == ALLOCATE_ERROR {
return self.parse_error_response(&data[20..20 + msg_len]);
}
if msg_type != ALLOCATE_RESPONSE {
return Err(TurnError::InvalidResponse(format!(
"Unexpected message type: 0x{:04x}",
msg_type
)));
}
let mut attrs = &data[20..20 + msg_len];
let mut relayed_addr = None;
let mut mapped_addr = None;
let mut lifetime = 600;
while attrs.len() >= 4 {
let attr_type = attrs.get_u16();
let attr_len = attrs.get_u16() as usize;
if attrs.len() < attr_len {
break;
}
let attr_data = &attrs[..attr_len];
match attr_type {
ATTR_XOR_RELAYED_ADDRESS => {
relayed_addr = parse_xor_address(attr_data, &self.transaction_id);
}
ATTR_XOR_MAPPED_ADDRESS => {
mapped_addr = parse_xor_address(attr_data, &self.transaction_id);
}
ATTR_LIFETIME => {
if attr_len >= 4 {
let mut lb = attr_data;
lifetime = lb.get_u32();
}
}
_ => {}
}
let padded_len = (attr_len + 3) & !3;
if attrs.len() >= padded_len {
attrs.advance(padded_len);
} else {
break;
}
}
let relayed = relayed_addr.ok_or(TurnError::NoRelayAddress)?;
let mapped =
mapped_addr.unwrap_or_else(|| SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0));
Ok(AllocateResult::Success(TurnAllocation {
relayed_addr: relayed,
mapped_addr: mapped,
lifetime,
realm: String::new(),
nonce: String::new(),
}))
}
fn parse_error_response(&self, attrs: &[u8]) -> Result<AllocateResult, TurnError> {
let mut buf = attrs;
let mut error_code = 0u16;
let mut error_reason = String::new();
let mut realm = None;
let mut nonce = None;
while buf.len() >= 4 {
let attr_type = buf.get_u16();
let attr_len = buf.get_u16() as usize;
if buf.len() < attr_len {
break;
}
let attr_data = &buf[..attr_len];
match attr_type {
ATTR_ERROR_CODE if attr_len >= 4 => {
let _reserved = u16::from_be_bytes([attr_data[0], attr_data[1]]);
let class = attr_data[2];
let number = attr_data[3];
error_code = (class as u16) * 100 + (number as u16);
if attr_len > 4 {
error_reason = String::from_utf8_lossy(&attr_data[4..]).to_string();
}
}
ATTR_REALM => {
realm = Some(String::from_utf8_lossy(attr_data).to_string());
}
ATTR_NONCE => {
nonce = Some(String::from_utf8_lossy(attr_data).to_string());
}
_ => {}
}
let padded_len = (attr_len + 3) & !3;
if buf.len() >= padded_len {
buf.advance(padded_len);
} else {
break;
}
}
if error_code == 401 {
if let (Some(r), Some(n)) = (realm, nonce) {
return Ok(AllocateResult::AuthRequired { realm: r, nonce: n });
}
}
Err(TurnError::ErrorResponse {
code: error_code,
reason: error_reason,
})
}
fn parse_refresh_response(&self, data: &[u8]) -> Result<u32, TurnError> {
if data.len() < 20 {
return Err(TurnError::InvalidResponse("Message too short".into()));
}
let mut buf = data;
let msg_type = buf.get_u16();
let msg_len = buf.get_u16() as usize;
if msg_type != REFRESH_RESPONSE {
return Err(TurnError::InvalidResponse(format!(
"Unexpected message type: 0x{:04x}",
msg_type
)));
}
let attrs = &data[20..20 + msg_len];
let mut buf = attrs;
let mut lifetime = 600;
while buf.len() >= 4 {
let attr_type = buf.get_u16();
let attr_len = buf.get_u16() as usize;
if buf.len() < attr_len {
break;
}
if attr_type == ATTR_LIFETIME && attr_len >= 4 {
let attr_data = &buf[..attr_len];
let mut lb = attr_data;
lifetime = lb.get_u32();
}
let padded_len = (attr_len + 3) & !3;
if buf.len() >= padded_len {
buf.advance(padded_len);
} else {
break;
}
}
Ok(lifetime)
}
fn parse_permission_response(&self, data: &[u8]) -> Result<(), TurnError> {
if data.len() < 20 {
return Err(TurnError::InvalidResponse("Message too short".into()));
}
let mut buf = data;
let msg_type = buf.get_u16();
if msg_type != CREATE_PERMISSION_RESPONSE {
return Err(TurnError::InvalidResponse(format!(
"Unexpected message type: 0x{:04x}",
msg_type
)));
}
Ok(())
}
fn parse_data_indication(&self, data: &[u8]) -> Result<(SocketAddr, Vec<u8>), TurnError> {
if data.len() < 20 {
return Err(TurnError::InvalidResponse("Message too short".into()));
}
let mut buf = data;
let msg_type = buf.get_u16();
let msg_len = buf.get_u16() as usize;
let _cookie = buf.get_u32();
let mut txn_id = [0u8; 12];
buf.copy_to_slice(&mut txn_id);
if msg_type != DATA_INDICATION {
return Err(TurnError::InvalidResponse(format!(
"Expected Data indication, got 0x{:04x}",
msg_type
)));
}
let mut attrs = &data[20..20 + msg_len];
let mut peer_addr = None;
let mut payload = None;
while attrs.len() >= 4 {
let attr_type = attrs.get_u16();
let attr_len = attrs.get_u16() as usize;
if attrs.len() < attr_len {
break;
}
let attr_data = &attrs[..attr_len];
match attr_type {
ATTR_XOR_PEER_ADDRESS => {
peer_addr = parse_xor_address(attr_data, &txn_id);
}
ATTR_DATA => {
payload = Some(attr_data.to_vec());
}
_ => {}
}
let padded_len = (attr_len + 3) & !3;
if attrs.len() >= padded_len {
attrs.advance(padded_len);
} else {
break;
}
}
match (peer_addr, payload) {
(Some(addr), Some(data)) => Ok((addr, data)),
_ => Err(TurnError::InvalidResponse(
"Missing peer address or data".into(),
)),
}
}
}
struct AuthContext {
username: String,
#[allow(dead_code)] password: String,
realm: String,
nonce: String,
}
enum AllocateResult {
Success(TurnAllocation),
AuthRequired { realm: String, nonce: String },
}
fn generate_transaction_id() -> [u8; 12] {
let mut id = [0u8; 12];
rand::thread_rng().fill_bytes(&mut id);
id
}
fn pad_to_4_bytes(buf: &mut BytesMut, len: usize) {
let padding = (4 - (len % 4)) % 4;
for _ in 0..padding {
buf.put_u8(0);
}
}
fn compute_long_term_key(username: &str, realm: &str, password: &str) -> [u8; 16] {
let credential = format!("{}:{}:{}", username, realm, password);
let digest = md5::compute(credential.as_bytes());
digest.0
}
fn add_message_integrity(msg: &mut BytesMut, username: &str, realm: &str, password: &str) {
let key = compute_long_term_key(username, realm, password);
let current_len = msg.len();
let new_len = (current_len - 20 + 24) as u16; msg[2] = (new_len >> 8) as u8;
msg[3] = (new_len & 0xFF) as u8;
let mut mac = HmacSha1::new_from_slice(&key).expect("HMAC can take key of any size");
mac.update(msg);
let result = mac.finalize();
let integrity = result.into_bytes();
msg.put_u16(ATTR_MESSAGE_INTEGRITY);
msg.put_u16(20); msg.put_slice(&integrity);
}
#[allow(dead_code)]
fn verify_message_integrity(msg: &[u8], username: &str, realm: &str, password: &str) -> bool {
if msg.len() < 20 {
return false;
}
let mut offset = 20; let mut integrity_offset = None;
while offset + 4 <= msg.len() {
let attr_type = u16::from_be_bytes([msg[offset], msg[offset + 1]]);
let attr_len = u16::from_be_bytes([msg[offset + 2], msg[offset + 3]]) as usize;
if attr_type == ATTR_MESSAGE_INTEGRITY {
integrity_offset = Some(offset);
break;
}
let padded_len = (attr_len + 3) & !3;
offset += 4 + padded_len;
}
let integrity_offset = match integrity_offset {
Some(o) => o,
None => return true, };
if integrity_offset + 24 > msg.len() {
return false;
}
let received_hmac = &msg[integrity_offset + 4..integrity_offset + 24];
let key = compute_long_term_key(username, realm, password);
let mut verify_msg = msg[..integrity_offset].to_vec();
let new_len = (integrity_offset - 20 + 24) as u16;
verify_msg[2] = (new_len >> 8) as u8;
verify_msg[3] = (new_len & 0xFF) as u8;
let mut mac = HmacSha1::new_from_slice(&key).expect("HMAC can take key of any size");
mac.update(&verify_msg);
let computed = mac.finalize().into_bytes();
computed.as_slice() == received_hmac
}
fn encode_xor_address(buf: &mut BytesMut, attr_type: u16, addr: SocketAddr, txn_id: &[u8; 12]) {
match addr.ip() {
IpAddr::V4(ipv4) => {
buf.put_u16(attr_type);
buf.put_u16(8);
buf.put_u8(0); buf.put_u8(AF_IPV4);
let xor_port = addr.port() ^ (MAGIC_COOKIE >> 16) as u16;
buf.put_u16(xor_port);
let ip_bytes = ipv4.octets();
let cookie_bytes = MAGIC_COOKIE.to_be_bytes();
buf.put_u8(ip_bytes[0] ^ cookie_bytes[0]);
buf.put_u8(ip_bytes[1] ^ cookie_bytes[1]);
buf.put_u8(ip_bytes[2] ^ cookie_bytes[2]);
buf.put_u8(ip_bytes[3] ^ cookie_bytes[3]);
}
IpAddr::V6(ipv6) => {
buf.put_u16(attr_type);
buf.put_u16(20);
buf.put_u8(0); buf.put_u8(AF_IPV6);
let xor_port = addr.port() ^ (MAGIC_COOKIE >> 16) as u16;
buf.put_u16(xor_port);
let ip_bytes = ipv6.octets();
let cookie_bytes = MAGIC_COOKIE.to_be_bytes();
for i in 0..4 {
buf.put_u8(ip_bytes[i] ^ cookie_bytes[i]);
}
for i in 0..12 {
buf.put_u8(ip_bytes[4 + i] ^ txn_id[i]);
}
}
}
}
fn parse_xor_address(data: &[u8], txn_id: &[u8; 12]) -> Option<SocketAddr> {
if data.len() < 4 {
return None;
}
let _reserved = data[0];
let family = data[1];
let xor_port = u16::from_be_bytes([data[2], data[3]]);
let port = xor_port ^ (MAGIC_COOKIE >> 16) as u16;
match family {
AF_IPV4 if data.len() >= 8 => {
let cookie_bytes = MAGIC_COOKIE.to_be_bytes();
let ip = Ipv4Addr::new(
data[4] ^ cookie_bytes[0],
data[5] ^ cookie_bytes[1],
data[6] ^ cookie_bytes[2],
data[7] ^ cookie_bytes[3],
);
Some(SocketAddr::new(IpAddr::V4(ip), port))
}
AF_IPV6 if data.len() >= 20 => {
let cookie_bytes = MAGIC_COOKIE.to_be_bytes();
let mut ip_bytes = [0u8; 16];
for i in 0..4 {
ip_bytes[i] = data[4 + i] ^ cookie_bytes[i];
}
for i in 0..12 {
ip_bytes[4 + i] = data[8 + i] ^ txn_id[i];
}
let ip = std::net::Ipv6Addr::from(ip_bytes);
Some(SocketAddr::new(IpAddr::V6(ip), port))
}
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Once;
fn init_tracing() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let _ = tracing_subscriber::fmt()
.with_max_level(tracing::Level::TRACE)
.with_test_writer()
.try_init();
});
}
#[test]
fn test_normalize_thread_id_branches() {
assert_eq!(normalize_thread_id(0), 1);
assert_eq!(normalize_thread_id(7), 7);
}
fn assert_turn_err_contains<T>(result: Result<T, TurnError>, needle: &str) {
assert!(result.is_err());
let err = result.err().unwrap();
assert!(format!("{err:?}").contains(needle));
}
fn as_success(result: AllocateResult) -> Option<TurnAllocation> {
match result {
AllocateResult::Success(alloc) => Some(alloc),
_ => None,
}
}
fn as_auth_required(result: AllocateResult) -> Option<(String, String)> {
match result {
AllocateResult::AuthRequired { realm, nonce } => Some((realm, nonce)),
_ => None,
}
}
#[test]
fn test_turn_error_io() {
let io_err = std::io::Error::other("test error");
let err: TurnError = io_err.into();
let msg = err.to_string();
assert!(msg.contains("IO error"));
}
#[test]
fn test_turn_error_timeout() {
let err = TurnError::Timeout;
assert_eq!(err.to_string(), "Request timeout");
}
#[test]
fn test_turn_error_invalid_response() {
let err = TurnError::InvalidResponse("bad data".to_string());
assert!(err.to_string().contains("Invalid response"));
assert!(err.to_string().contains("bad data"));
}
#[test]
fn test_turn_error_error_response() {
let err = TurnError::ErrorResponse {
code: 401,
reason: "Unauthorized".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("401"));
assert!(msg.contains("Unauthorized"));
}
#[test]
fn test_turn_error_auth_required() {
let err = TurnError::AuthRequired {
realm: "example.com".to_string(),
nonce: "abc123".to_string(),
};
assert!(err.to_string().contains("Authentication required"));
}
#[test]
fn test_turn_error_no_relay_address() {
let err = TurnError::NoRelayAddress;
assert!(err.to_string().contains("No relay address"));
}
#[test]
fn test_turn_error_not_allocated() {
let err = TurnError::NotAllocated;
assert!(err.to_string().contains("Allocation not active"));
}
#[test]
fn test_turn_error_debug() {
let err = TurnError::Timeout;
let debug = format!("{:?}", err);
assert!(debug.contains("Timeout"));
}
#[test]
fn test_turn_server_new() {
let server = TurnServer::new("1.2.3.4:3478".parse().unwrap(), "user", "pass");
assert_eq!(server.username, "user");
assert_eq!(server.password, "pass");
assert_eq!(server.addr.port(), 3478);
assert!(server.realm.is_none());
}
#[test]
fn test_turn_server_with_realm() {
let mut server = TurnServer::new(
"turn.example.com:3478"
.parse::<SocketAddr>()
.unwrap_or_else(|_| "1.2.3.4:3478".parse().unwrap()),
"testuser",
"testpass",
);
server.realm = Some("example.com".to_string());
assert_eq!(server.username, "testuser");
assert_eq!(server.password, "testpass");
assert_eq!(server.realm.as_deref(), Some("example.com"));
}
#[test]
fn test_turn_server_clone() {
let server = TurnServer::new("1.2.3.4:3478".parse().unwrap(), "user", "pass");
let cloned = server.clone();
assert_eq!(cloned.username, server.username);
assert_eq!(cloned.addr, server.addr);
}
#[test]
fn test_turn_server_debug() {
let server = TurnServer::new("1.2.3.4:3478".parse().unwrap(), "user", "pass");
let debug = format!("{:?}", server);
assert!(debug.contains("TurnServer"));
assert!(debug.contains("user"));
}
#[test]
fn test_turn_allocation() {
let alloc = TurnAllocation {
relayed_addr: "203.0.113.1:49152".parse().unwrap(),
mapped_addr: "192.0.2.1:12345".parse().unwrap(),
lifetime: 600,
realm: "example.com".to_string(),
nonce: "abc123def456".to_string(),
};
assert_eq!(alloc.relayed_addr.port(), 49152);
assert_eq!(alloc.mapped_addr.port(), 12345);
assert_eq!(alloc.lifetime, 600);
assert_eq!(alloc.realm, "example.com");
}
#[test]
fn test_turn_allocation_clone() {
let alloc = TurnAllocation {
relayed_addr: "203.0.113.1:49152".parse().unwrap(),
mapped_addr: "192.0.2.1:12345".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "nonce123".to_string(),
};
let cloned = alloc.clone();
assert_eq!(cloned.relayed_addr, alloc.relayed_addr);
assert_eq!(cloned.lifetime, alloc.lifetime);
}
#[test]
fn test_turn_allocation_debug() {
let alloc = TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 300,
realm: "realm".to_string(),
nonce: "nonce".to_string(),
};
let debug = format!("{:?}", alloc);
assert!(debug.contains("TurnAllocation"));
}
#[test]
fn test_generate_transaction_id() {
let id1 = generate_transaction_id();
let id2 = generate_transaction_id();
assert_ne!(id1, id2);
assert_eq!(id1.len(), 12);
}
#[test]
fn test_generate_transaction_id_randomness() {
let ids: Vec<_> = (0..10).map(|_| generate_transaction_id()).collect();
for i in 0..ids.len() {
for j in (i + 1)..ids.len() {
assert_ne!(ids[i], ids[j]);
}
}
}
#[test]
fn test_xor_address_encode_decode_ipv4() {
let txn_id = [0x11u8; 12];
let addr: SocketAddr = "192.168.1.100:12345".parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_PEER_ADDRESS, addr, &txn_id);
let encoded = &buf[4..];
let decoded = parse_xor_address(encoded, &txn_id).unwrap();
assert_eq!(decoded, addr);
}
#[test]
fn test_xor_address_encode_decode_ipv6() {
let txn_id = [0x22u8; 12];
let addr: SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_PEER_ADDRESS, addr, &txn_id);
let encoded = &buf[4..];
let decoded = parse_xor_address(encoded, &txn_id).unwrap();
assert_eq!(decoded, addr);
}
#[test]
fn test_xor_address_different_ports() {
let txn_id = [0x33u8; 12];
for port in [80, 443, 5060, 5061, 3478, 49152, 65535] {
let addr: SocketAddr = format!("10.0.0.1:{}", port).parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_MAPPED_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
}
#[test]
fn test_parse_xor_address_too_short() {
let txn_id = [0u8; 12];
assert!(parse_xor_address(&[0, 1, 2], &txn_id).is_none());
}
#[test]
fn test_parse_xor_address_invalid_family() {
let txn_id = [0u8; 12];
let data = [0, 0x03, 0, 0, 0, 0, 0, 0];
assert!(parse_xor_address(&data, &txn_id).is_none());
}
#[test]
fn test_parse_xor_address_ipv4_too_short() {
let txn_id = [0u8; 12];
let data = [0, AF_IPV4, 0, 0, 0, 0, 0]; assert!(parse_xor_address(&data, &txn_id).is_none());
}
#[test]
fn test_parse_xor_address_ipv6_too_short() {
let txn_id = [0u8; 12];
let data = [0, AF_IPV6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; assert!(parse_xor_address(&data, &txn_id).is_none());
}
#[test]
fn test_pad_to_4_bytes() {
let mut buf = BytesMut::new();
buf.put_slice(b"abc");
pad_to_4_bytes(&mut buf, 3);
assert_eq!(buf.len(), 4);
let mut buf = BytesMut::new();
buf.put_slice(b"abcd");
pad_to_4_bytes(&mut buf, 4);
assert_eq!(buf.len(), 4);
let mut buf = BytesMut::new();
buf.put_slice(b"ab");
pad_to_4_bytes(&mut buf, 2);
assert_eq!(buf.len(), 4);
}
#[test]
fn test_pad_to_4_bytes_single() {
let mut buf = BytesMut::new();
buf.put_slice(b"a");
pad_to_4_bytes(&mut buf, 1);
assert_eq!(buf.len(), 4);
}
#[test]
fn test_pad_to_4_bytes_empty() {
let mut buf = BytesMut::new();
pad_to_4_bytes(&mut buf, 0);
assert_eq!(buf.len(), 0); }
#[test]
fn test_pad_to_4_bytes_various() {
for len in 0..20 {
let mut buf = BytesMut::new();
buf.extend_from_slice(&vec![0u8; len]);
pad_to_4_bytes(&mut buf, len);
assert_eq!(buf.len() % 4, 0);
}
}
#[test]
fn test_compute_long_term_key() {
let key = compute_long_term_key("user", "realm.org", "password");
assert_eq!(key.len(), 16);
let key2 = compute_long_term_key("user2", "realm.org", "password");
assert_ne!(key, key2);
}
#[test]
fn test_compute_long_term_key_deterministic() {
let key1 = compute_long_term_key("alice", "example.com", "secret");
let key2 = compute_long_term_key("alice", "example.com", "secret");
assert_eq!(key1, key2);
}
#[test]
fn test_compute_long_term_key_different_realm() {
let key1 = compute_long_term_key("user", "realm1.com", "pass");
let key2 = compute_long_term_key("user", "realm2.com", "pass");
assert_ne!(key1, key2);
}
#[test]
fn test_compute_long_term_key_different_password() {
let key1 = compute_long_term_key("user", "realm", "pass1");
let key2 = compute_long_term_key("user", "realm", "pass2");
assert_ne!(key1, key2);
}
#[test]
fn test_message_integrity_roundtrip() {
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(4); msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&[0x11u8; 12]);
msg.put_u16(ATTR_REQUESTED_TRANSPORT);
msg.put_u16(4);
msg.put_u8(TRANSPORT_UDP);
msg.put_u8(0);
msg.put_u8(0);
msg.put_u8(0);
let username = "testuser";
let realm = "testrealm";
let password = "testpass";
add_message_integrity(&mut msg, username, realm, password);
assert_eq!(msg.len(), 20 + 4 + 4 + 24);
assert!(verify_message_integrity(&msg, username, realm, password));
assert!(!verify_message_integrity(
&msg,
username,
realm,
"wrongpass"
));
}
#[test]
fn test_message_integrity_no_attribute() {
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(4);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&[0x22u8; 12]);
msg.put_u16(ATTR_REQUESTED_TRANSPORT);
msg.put_u16(4);
msg.put_u8(TRANSPORT_UDP);
msg.put_u8(0);
msg.put_u8(0);
msg.put_u8(0);
assert!(verify_message_integrity(&msg, "user", "realm", "pass"));
}
#[test]
fn test_message_integrity_too_short() {
let msg = [0u8; 10];
assert!(!verify_message_integrity(&msg, "user", "realm", "pass"));
}
#[test]
fn test_message_integrity_truncated_attribute() {
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(24); msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&[0x55u8; 12]);
msg.put_u16(ATTR_MESSAGE_INTEGRITY);
msg.put_u16(20);
msg.put_slice(&[0u8; 10]);
assert!(!verify_message_integrity(&msg, "user", "realm", "pass"));
}
#[test]
fn test_message_integrity_wrong_username() {
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(4);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&[0x33u8; 12]);
msg.put_u16(ATTR_REQUESTED_TRANSPORT);
msg.put_u16(4);
msg.put_u8(TRANSPORT_UDP);
msg.put_u8(0);
msg.put_u8(0);
msg.put_u8(0);
add_message_integrity(&mut msg, "correct_user", "realm", "pass");
assert!(!verify_message_integrity(
&msg,
"wrong_user",
"realm",
"pass"
));
}
#[test]
fn test_message_integrity_wrong_realm() {
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(4);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&[0x44u8; 12]);
msg.put_u16(ATTR_REQUESTED_TRANSPORT);
msg.put_u16(4);
msg.put_u8(TRANSPORT_UDP);
msg.put_u8(0);
msg.put_u8(0);
msg.put_u8(0);
add_message_integrity(&mut msg, "user", "correct_realm", "pass");
assert!(!verify_message_integrity(
&msg,
"user",
"wrong_realm",
"pass"
));
}
#[test]
fn test_magic_cookie() {
assert_eq!(MAGIC_COOKIE, 0x2112A442);
}
#[test]
fn test_address_families() {
assert_eq!(AF_IPV4, 0x01);
assert_eq!(AF_IPV6, 0x02);
}
#[test]
fn test_transport_udp() {
assert_eq!(TRANSPORT_UDP, 17);
}
#[test]
fn test_message_types() {
assert_eq!(ALLOCATE_REQUEST, 0x0003);
assert_eq!(ALLOCATE_RESPONSE, 0x0103);
assert_eq!(ALLOCATE_ERROR, 0x0113);
assert_eq!(REFRESH_REQUEST, 0x0004);
assert_eq!(REFRESH_RESPONSE, 0x0104);
assert_eq!(SEND_INDICATION, 0x0016);
assert_eq!(DATA_INDICATION, 0x0017);
assert_eq!(CREATE_PERMISSION_REQUEST, 0x0008);
assert_eq!(CREATE_PERMISSION_RESPONSE, 0x0108);
}
#[test]
fn test_attribute_types() {
assert_eq!(ATTR_XOR_MAPPED_ADDRESS, 0x0020);
assert_eq!(ATTR_XOR_RELAYED_ADDRESS, 0x0016);
assert_eq!(ATTR_XOR_PEER_ADDRESS, 0x0012);
assert_eq!(ATTR_LIFETIME, 0x000D);
assert_eq!(ATTR_DATA, 0x0013);
assert_eq!(ATTR_REQUESTED_TRANSPORT, 0x0019);
assert_eq!(ATTR_USERNAME, 0x0006);
assert_eq!(ATTR_REALM, 0x0014);
assert_eq!(ATTR_NONCE, 0x0015);
assert_eq!(ATTR_MESSAGE_INTEGRITY, 0x0008);
assert_eq!(ATTR_ERROR_CODE, 0x0009);
}
#[test]
fn test_xor_address_loopback() {
let txn_id = [0xAA; 12];
let addr: SocketAddr = "127.0.0.1:1234".parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_MAPPED_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
#[test]
fn test_xor_address_ipv6_loopback() {
let txn_id = [0xBB; 12];
let addr: SocketAddr = "[::1]:5060".parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_MAPPED_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
#[test]
fn test_xor_address_full_ipv6() {
let txn_id = [0xCC; 12];
let addr: SocketAddr = "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:443"
.parse()
.unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_RELAYED_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
#[test]
fn test_turn_client_build_allocate_request_no_auth() {
let _server = TurnServer::new("1.2.3.4:3478".parse().unwrap(), "user", "pass");
let transaction_id = generate_transaction_id();
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_REQUESTED_TRANSPORT);
attrs.put_u16(4);
attrs.put_u8(TRANSPORT_UDP);
attrs.put_u8(0);
attrs.put_u8(0);
attrs.put_u8(0);
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&transaction_id);
msg.put_slice(&attrs);
assert_eq!(msg.len(), 28); assert_eq!(&msg[0..2], &ALLOCATE_REQUEST.to_be_bytes());
}
#[test]
fn test_turn_client_build_allocate_request_with_auth() {
let transaction_id = generate_transaction_id();
let auth = AuthContext {
username: "testuser".to_string(),
password: "testpass".to_string(),
realm: "testrealm".to_string(),
nonce: "testnonce12345".to_string(),
};
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_REQUESTED_TRANSPORT);
attrs.put_u16(4);
attrs.put_u8(TRANSPORT_UDP);
attrs.put_u8(0);
attrs.put_u8(0);
attrs.put_u8(0);
let username_bytes = auth.username.as_bytes();
attrs.put_u16(ATTR_USERNAME);
attrs.put_u16(username_bytes.len() as u16);
attrs.put_slice(username_bytes);
pad_to_4_bytes(&mut attrs, username_bytes.len());
let realm_bytes = auth.realm.as_bytes();
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm_bytes.len() as u16);
attrs.put_slice(realm_bytes);
pad_to_4_bytes(&mut attrs, realm_bytes.len());
let nonce_bytes = auth.nonce.as_bytes();
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce_bytes.len() as u16);
attrs.put_slice(nonce_bytes);
pad_to_4_bytes(&mut attrs, nonce_bytes.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len() + 24);
msg.put_u16(ALLOCATE_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&transaction_id);
msg.put_slice(&attrs);
add_message_integrity(&mut msg, &auth.username, &auth.realm, &auth.password);
assert!(msg.len() > 28); }
#[test]
fn test_build_refresh_request() {
let transaction_id = generate_transaction_id();
let lifetime = 600u32;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(REFRESH_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&transaction_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &REFRESH_REQUEST.to_be_bytes());
}
#[test]
fn test_build_permission_request() {
let transaction_id = generate_transaction_id();
let peer_addr: SocketAddr = "192.168.1.100:5060".parse().unwrap();
let mut attrs = BytesMut::new();
encode_xor_address(
&mut attrs,
ATTR_XOR_PEER_ADDRESS,
peer_addr,
&transaction_id,
);
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(CREATE_PERMISSION_REQUEST);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&transaction_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &CREATE_PERMISSION_REQUEST.to_be_bytes());
}
#[test]
fn test_build_send_indication() {
let transaction_id = generate_transaction_id();
let peer_addr: SocketAddr = "10.0.0.1:12345".parse().unwrap();
let data = b"test payload data";
let mut attrs = BytesMut::new();
encode_xor_address(
&mut attrs,
ATTR_XOR_PEER_ADDRESS,
peer_addr,
&transaction_id,
);
attrs.put_u16(ATTR_DATA);
attrs.put_u16(data.len() as u16);
attrs.put_slice(data);
pad_to_4_bytes(&mut attrs, data.len());
let mut msg = BytesMut::with_capacity(20 + attrs.len());
msg.put_u16(SEND_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&transaction_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &SEND_INDICATION.to_be_bytes());
}
#[test]
fn test_parse_allocate_response_success() {
let txn_id = [0x11u8; 12];
let relayed_addr: SocketAddr = "203.0.113.1:49152".parse().unwrap();
let mapped_addr: SocketAddr = "192.0.2.1:54321".parse().unwrap();
let lifetime = 600u32;
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_RELAYED_ADDRESS, relayed_addr, &txn_id);
encode_xor_address(&mut attrs, ATTR_XOR_MAPPED_ADDRESS, mapped_addr, &txn_id);
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &ALLOCATE_RESPONSE.to_be_bytes());
}
#[test]
fn test_parse_allocate_error_response() {
let txn_id = [0x22u8; 12];
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(8); attrs.put_u16(0); attrs.put_u8(4); attrs.put_u8(1); attrs.put_slice(b"Auth");
let realm = b"example.com";
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm.len() as u16);
attrs.put_slice(realm);
pad_to_4_bytes(&mut attrs, realm.len());
let nonce = b"dcd98b7102dd2f0e8b11d0f600bfb0c093";
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce.len() as u16);
attrs.put_slice(nonce);
pad_to_4_bytes(&mut attrs, nonce.len());
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_ERROR);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &ALLOCATE_ERROR.to_be_bytes());
}
#[test]
fn test_parse_refresh_response() {
let txn_id = [0x33u8; 12];
let lifetime = 300u32;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let mut msg = BytesMut::new();
msg.put_u16(REFRESH_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &REFRESH_RESPONSE.to_be_bytes());
}
#[test]
fn test_parse_permission_response() {
let txn_id = [0x44u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(CREATE_PERMISSION_RESPONSE);
msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
assert_eq!(&msg[0..2], &CREATE_PERMISSION_RESPONSE.to_be_bytes());
}
#[test]
fn test_parse_data_indication() {
let txn_id = [0x55u8; 12];
let peer_addr: SocketAddr = "10.0.0.1:5060".parse().unwrap();
let data = b"Hello, World!";
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_PEER_ADDRESS, peer_addr, &txn_id);
attrs.put_u16(ATTR_DATA);
attrs.put_u16(data.len() as u16);
attrs.put_slice(data);
pad_to_4_bytes(&mut attrs, data.len());
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
assert_eq!(&msg[0..2], &DATA_INDICATION.to_be_bytes());
}
#[test]
fn test_error_code_parsing() {
let class: u16 = 4;
let number: u16 = 1;
let error_code = class * 100 + number;
assert_eq!(error_code, 401);
let class: u16 = 4;
let number: u16 = 38;
let error_code = class * 100 + number;
assert_eq!(error_code, 438);
let class: u16 = 5;
let number: u16 = 0;
let error_code = class * 100 + number;
assert_eq!(error_code, 500);
let class: u16 = 3;
let number: u16 = 0;
let error_code = class * 100 + number;
assert_eq!(error_code, 300); }
#[test]
fn test_xor_mapped_address_attribute_type() {
assert_eq!(ATTR_XOR_MAPPED_ADDRESS, 0x0020);
}
#[test]
fn test_xor_relayed_address_attribute_type() {
assert_eq!(ATTR_XOR_RELAYED_ADDRESS, 0x0016);
}
#[test]
fn test_channel_bind_message_types() {
assert_eq!(CHANNEL_BIND_REQUEST, 0x0009);
assert_eq!(CHANNEL_BIND_RESPONSE, 0x0109);
}
#[test]
fn test_channel_number_attribute_type() {
assert_eq!(ATTR_CHANNEL_NUMBER, 0x000C);
}
#[test]
fn test_auth_context_fields() {
let auth = AuthContext {
username: "alice".to_string(),
password: "secret123".to_string(),
realm: "turn.example.org".to_string(),
nonce: "1234567890abcdef".to_string(),
};
assert_eq!(auth.username, "alice");
assert_eq!(auth.password, "secret123");
assert_eq!(auth.realm, "turn.example.org");
assert_eq!(auth.nonce, "1234567890abcdef");
}
#[test]
fn test_allocate_result_variants() {
let alloc = TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test".to_string(),
nonce: "nonce".to_string(),
};
let result = AllocateResult::Success(alloc);
let alloc = as_success(result).expect("expected success");
assert_eq!(alloc.lifetime, 600);
let result = AllocateResult::AuthRequired {
realm: "example.com".to_string(),
nonce: "abc123".to_string(),
};
let (realm, nonce) = as_auth_required(result).expect("expected auth");
assert_eq!(realm, "example.com");
assert_eq!(nonce, "abc123");
}
#[test]
fn test_turn_allocation_zero_lifetime() {
let alloc = TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 0,
realm: "test".to_string(),
nonce: "nonce".to_string(),
};
assert_eq!(alloc.lifetime, 0);
}
#[test]
fn test_encode_xor_address_ipv4_various() {
let txn_id = [0xDE; 12];
let test_cases = [
"0.0.0.0:0",
"255.255.255.255:65535",
"192.168.0.1:80",
"10.0.0.1:443",
"172.16.0.1:8080",
];
for addr_str in &test_cases {
let addr: SocketAddr = addr_str.parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_MAPPED_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
}
#[test]
fn test_encode_xor_address_ipv6_various() {
let txn_id = [0xEF; 12];
let test_cases = [
"[::]:0",
"[::1]:80",
"[fe80::1]:443",
"[2001:db8::1]:5060",
"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535",
];
for addr_str in &test_cases {
let addr: SocketAddr = addr_str.parse().unwrap();
let mut buf = BytesMut::new();
encode_xor_address(&mut buf, ATTR_XOR_PEER_ADDRESS, addr, &txn_id);
let decoded = parse_xor_address(&buf[4..], &txn_id).unwrap();
assert_eq!(decoded, addr);
}
}
struct MockTurnServer {
socket: UdpSocket,
addr: SocketAddr,
}
impl MockTurnServer {
async fn new() -> Self {
let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
Self { socket, addr }
}
fn build_allocate_success(
txn_id: &[u8; 12],
relayed_addr: SocketAddr,
mapped_addr: SocketAddr,
lifetime: u32,
) -> Vec<u8> {
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_RELAYED_ADDRESS, relayed_addr, txn_id);
encode_xor_address(&mut attrs, ATTR_XOR_MAPPED_ADDRESS, mapped_addr, txn_id);
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.put_slice(&attrs);
msg.to_vec()
}
fn build_auth_required(txn_id: &[u8; 12], realm: &str, nonce: &str) -> Vec<u8> {
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(4);
attrs.put_u16(0); attrs.put_u8(4); attrs.put_u8(1);
let realm_bytes = realm.as_bytes();
attrs.put_u16(ATTR_REALM);
attrs.put_u16(realm_bytes.len() as u16);
attrs.put_slice(realm_bytes);
pad_to_4_bytes(&mut attrs, realm_bytes.len());
let nonce_bytes = nonce.as_bytes();
attrs.put_u16(ATTR_NONCE);
attrs.put_u16(nonce_bytes.len() as u16);
attrs.put_slice(nonce_bytes);
pad_to_4_bytes(&mut attrs, nonce_bytes.len());
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_ERROR);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.put_slice(&attrs);
msg.to_vec()
}
fn build_refresh_success(txn_id: &[u8; 12], lifetime: u32) -> Vec<u8> {
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(lifetime);
let mut msg = BytesMut::new();
msg.put_u16(REFRESH_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.put_slice(&attrs);
msg.to_vec()
}
fn build_permission_success(txn_id: &[u8; 12]) -> Vec<u8> {
let mut msg = BytesMut::new();
msg.put_u16(CREATE_PERMISSION_RESPONSE);
msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.to_vec()
}
fn build_data_indication(txn_id: &[u8; 12], peer_addr: SocketAddr, data: &[u8]) -> Vec<u8> {
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_PEER_ADDRESS, peer_addr, txn_id);
attrs.put_u16(ATTR_DATA);
attrs.put_u16(data.len() as u16);
attrs.put_slice(data);
pad_to_4_bytes(&mut attrs, data.len());
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.put_slice(&attrs);
msg.to_vec()
}
fn build_error_response(txn_id: &[u8; 12], code: u16, reason: &str) -> Vec<u8> {
let mut attrs = BytesMut::new();
let class = (code / 100) as u8;
let number = (code % 100) as u8;
let reason_bytes = reason.as_bytes();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(4 + reason_bytes.len() as u16);
attrs.put_u16(0); attrs.put_u8(class);
attrs.put_u8(number);
attrs.put_slice(reason_bytes);
pad_to_4_bytes(&mut attrs, 4 + reason_bytes.len());
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_ERROR);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(txn_id);
msg.put_slice(&attrs);
msg.to_vec()
}
}
#[tokio::test]
async fn test_turn_client_new() {
init_tracing();
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await;
assert!(client.is_ok());
let client = client.unwrap();
assert!(client.local_addr().is_ok());
assert!(client.allocation().is_none());
assert!(client.relayed_addr().is_none());
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_new_forced_bind_error() {
force_bind_error_once();
let server = TurnServer::new("127.0.0.1:3478".parse().unwrap(), "user", "pass");
let result = TurnClient::new(server).await;
assert_turn_err_contains(result, "Io");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_new_forced_connect_error() {
force_connect_error_once();
let server = TurnServer::new("127.0.0.1:3478".parse().unwrap(), "user", "pass");
let result = TurnClient::new(server).await;
assert_turn_err_contains(result, "Io");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_new_forced_local_addr_error() {
force_local_addr_error_once();
let server = TurnServer::new("127.0.0.1:3478".parse().unwrap(), "user", "pass");
let result = TurnClient::new(server).await;
assert_turn_err_contains(result, "Io");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_local_addr_forced_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
force_local_addr_error_once();
let result = client.local_addr();
assert_turn_err_contains(result, "Io");
}
#[tokio::test]
async fn test_turn_client_send_raw_zero_retries_timeout() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.retries = 0;
let result = client.send_raw(b"").await;
assert_turn_err_contains(result, "Timeout");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_send_raw_forced_send_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
force_send_error_once();
let result = client.send_raw(b"test").await;
assert_turn_err_contains(result, "forced send error");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_send_raw_forced_recv_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
force_recv_error_once();
let result = client.send_raw(b"test").await;
assert_turn_err_contains(result, "forced recv error");
}
#[tokio::test]
async fn test_turn_client_allocate_success() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response =
MockTurnServer::build_auth_required(&txn_id, "test.realm.com", "nonce123456789");
mock_socket.send_to(&response, peer).await.unwrap();
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let relayed: SocketAddr = "203.0.113.1:49152".parse().unwrap();
let mapped: SocketAddr = "192.0.2.1:54321".parse().unwrap();
let response = MockTurnServer::build_allocate_success(&txn_id, relayed, mapped, 600);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.allocate().await;
assert!(result.is_ok());
let alloc = result.unwrap();
assert_eq!(alloc.relayed_addr.port(), 49152);
assert_eq!(alloc.mapped_addr.port(), 54321);
assert_eq!(alloc.lifetime, 600);
assert!(client.allocation().is_some());
assert!(client.relayed_addr().is_some());
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_allocate_auth_send_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response =
MockTurnServer::build_auth_required(&txn_id, "test.realm.com", "nonce123456789");
mock_socket.send_to(&response, peer).await.unwrap();
});
force_auth_send_error_once();
let result = client.allocate().await;
assert_turn_err_contains(result, "forced auth send error");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_allocate_auth_send_request_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
force_send_error_once();
let response =
MockTurnServer::build_auth_required(&txn_id, "test.realm.com", "nonce123456789");
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.allocate().await;
assert_turn_err_contains(result, "forced send error");
}
#[tokio::test]
async fn test_turn_client_allocate_direct_success() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let relayed: SocketAddr = "1.2.3.4:12345".parse().unwrap();
let mapped: SocketAddr = "5.6.7.8:54321".parse().unwrap();
let response = MockTurnServer::build_allocate_success(&txn_id, relayed, mapped, 300);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.allocate().await;
assert!(result.is_ok());
assert_eq!(result.unwrap().lifetime, 300);
}
#[tokio::test]
async fn test_turn_client_refresh() {
init_tracing();
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_refresh_success(&txn_id, 300);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.refresh(300).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 300);
assert_eq!(client.allocation().unwrap().lifetime, 300);
}
#[tokio::test]
async fn test_turn_client_refresh_parse_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_permission_success(&txn_id);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.refresh(300).await;
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_refresh_send_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
force_send_error_once();
let result = client.refresh(300).await;
assert_turn_err_contains(result, "forced send error");
}
#[tokio::test]
async fn test_turn_client_refresh_not_allocated() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let result = client.refresh(300).await;
assert_turn_err_contains(result, "NotAllocated");
}
#[tokio::test]
async fn test_turn_client_create_permission() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let peer_addr: SocketAddr = "10.0.0.100:5060".parse().unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_permission_success(&txn_id);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.create_permission(peer_addr).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_turn_client_create_permission_parse_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let peer_addr: SocketAddr = "10.0.0.100:5060".parse().unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_refresh_success(&txn_id, 300);
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.create_permission(peer_addr).await;
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_create_permission_send_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let peer_addr: SocketAddr = "10.0.0.100:5060".parse().unwrap();
force_send_error_once();
let result = client.create_permission(peer_addr).await;
assert_turn_err_contains(result, "forced send error");
}
#[tokio::test]
async fn test_turn_client_create_permission_not_allocated() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let peer_addr: SocketAddr = "10.0.0.100:5060".parse().unwrap();
let result = client.create_permission(peer_addr).await;
assert_turn_err_contains(result, "NotAllocated");
}
#[tokio::test]
async fn test_turn_client_send_data() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
let peer: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let result = client.send_data(peer, b"test data").await;
assert_turn_err_contains(result, "NotAllocated");
}
#[tokio::test]
async fn test_turn_client_send_data_with_allocation() {
init_tracing();
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let peer: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let result = client.send_data(peer, b"test data").await;
assert!(result.is_ok());
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_send_data_forced_send_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.allocation = Some(TurnAllocation {
relayed_addr: "1.2.3.4:5000".parse().unwrap(),
mapped_addr: "5.6.7.8:6000".parse().unwrap(),
lifetime: 600,
realm: "test.realm".to_string(),
nonce: "testnonce".to_string(),
});
let peer: SocketAddr = "10.0.0.1:5000".parse().unwrap();
force_send_error_once();
let result = client.send_data(peer, b"test data").await;
assert_turn_err_contains(result, "forced send error");
}
#[tokio::test]
async fn test_turn_client_recv_data() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
let peer_addr: SocketAddr = "10.0.0.50:9999".parse().unwrap();
let test_data = b"hello from peer";
let mock_socket = mock.socket;
let client_addr = client.local_addr().unwrap();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
let txn_id = [0x11u8; 12];
let indication = MockTurnServer::build_data_indication(&txn_id, peer_addr, test_data);
mock_socket.send_to(&indication, client_addr).await.unwrap();
});
let result = client.recv_data_timeout(Duration::from_secs(1)).await;
assert!(result.is_ok());
let (addr, data) = result.unwrap();
assert_eq!(addr, peer_addr);
assert_eq!(data, test_data);
}
#[tokio::test(flavor = "current_thread")]
async fn test_turn_client_recv_data_forced_recv_error() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
force_recv_error_once();
let result = client.recv_data().await;
assert_turn_err_contains(result, "forced recv error");
}
#[tokio::test]
async fn test_turn_client_recv_data_timeout() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let client = TurnClient::new(server).await.unwrap();
let result = client.recv_data_timeout(Duration::from_millis(100)).await;
assert_turn_err_contains(result, "Timeout");
}
#[tokio::test]
async fn test_turn_client_allocate_timeout() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
client.timeout = Duration::from_millis(50);
client.retries = 1;
let result = client.allocate().await;
assert_turn_err_contains(result, "Timeout");
}
#[tokio::test]
async fn test_turn_client_allocate_error_response() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_error_response(&txn_id, 403, "Forbidden");
mock_socket.send_to(&response, peer).await.unwrap();
});
let result = client.allocate().await;
assert_turn_err_contains(result, "403");
}
#[tokio::test]
async fn test_turn_client_allocate_double_auth() {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "testuser", "testpass");
let mut client = TurnClient::new(server).await.unwrap();
let mock_socket = mock.socket;
tokio::spawn(async move {
for _ in 0..2 {
let mut buf = [0u8; 4096];
let (_, peer) = mock_socket.recv_from(&mut buf).await.unwrap();
let mut txn_id = [0u8; 12];
txn_id.copy_from_slice(&buf[8..20]);
let response = MockTurnServer::build_auth_required(&txn_id, "realm", "nonce");
mock_socket.send_to(&response, peer).await.unwrap();
}
});
let result = client.allocate().await;
assert_turn_err_contains(result, "401");
}
async fn create_test_client(txn_id: [u8; 12]) -> TurnClient {
let mock = MockTurnServer::new().await;
let server = TurnServer::new(mock.addr, "user", "pass");
let mut client = TurnClient::new(server).await.unwrap();
client.transaction_id = txn_id;
client
}
#[tokio::test]
async fn test_send_raw_timeout_retries() {
let server_socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
let server_addr = server_socket.local_addr().unwrap();
let server = TurnServer::new(server_addr, "user", "pass");
let mut client = TurnClient::new(server).await.unwrap();
client.retries = 2;
client.timeout = std::time::Duration::from_millis(10);
let request = vec![0u8; 20];
let result = client.send_raw(&request).await;
assert_turn_err_contains(result, "Timeout");
}
#[tokio::test]
async fn test_parse_allocate_response_too_short() {
let client = create_test_client([0x11; 12]).await;
let short_data = [0u8; 10];
let result = client.parse_allocate_response(&short_data);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_allocate_response_wrong_txn_id() {
let txn_id = [0x11u8; 12];
let relayed: SocketAddr = "1.2.3.4:5000".parse().unwrap();
let mapped: SocketAddr = "5.6.7.8:6000".parse().unwrap();
let response = MockTurnServer::build_allocate_success(&txn_id, relayed, mapped, 600);
let client = create_test_client([0x22; 12]).await;
let result = client.parse_allocate_response(&response);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_allocate_response_unexpected_type() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(0x9999); msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
let client = create_test_client(txn_id).await;
let result = client.parse_allocate_response(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_allocate_response_no_relay_addr() {
let txn_id = [0x11u8; 12];
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(4);
attrs.put_u32(600);
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let client = create_test_client(txn_id).await;
let result = client.parse_allocate_response(&msg);
assert_turn_err_contains(result, "NoRelayAddress");
}
#[tokio::test]
async fn test_parse_allocate_response_ignores_unknown_attr() {
let txn_id = [0x11u8; 12];
let relayed_addr: SocketAddr = "203.0.113.1:49152".parse().unwrap();
let mapped_addr: SocketAddr = "192.0.2.1:54321".parse().unwrap();
let mut attrs = BytesMut::new();
attrs.put_u16(0xFFFF);
attrs.put_u16(4);
attrs.put_slice(&[1, 2, 3, 4]);
encode_xor_address(&mut attrs, ATTR_XOR_RELAYED_ADDRESS, relayed_addr, &txn_id);
encode_xor_address(&mut attrs, ATTR_XOR_MAPPED_ADDRESS, mapped_addr, &txn_id);
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let client = create_test_client(txn_id).await;
let result = client.parse_allocate_response(&msg).unwrap();
let allocation = as_success(result).expect("expected success");
assert_eq!(allocation.relayed_addr, relayed_addr);
assert_eq!(allocation.mapped_addr, mapped_addr);
}
#[tokio::test]
async fn test_parse_allocate_response_truncated_attr_len() {
use bytes::{BufMut, BytesMut};
let txn_id = [0x11u8; 12];
let client = create_test_client(txn_id).await;
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(4);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_u16(ATTR_XOR_RELAYED_ADDRESS);
msg.put_u16(8);
let result = client.parse_allocate_response(&msg);
assert_turn_err_contains(result, "NoRelayAddress");
}
#[tokio::test]
async fn test_parse_allocate_response_truncated_padding() {
use bytes::{BufMut, BytesMut};
let txn_id = [0x11u8; 12];
let relayed_addr: SocketAddr = "203.0.113.1:49152".parse().unwrap();
let client = create_test_client(txn_id).await;
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_RELAYED_ADDRESS, relayed_addr, &txn_id);
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(1);
attrs.put_u8(1);
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let result = client.parse_allocate_response(&msg).unwrap();
let allocation = as_success(result).expect("Expected success result");
assert_eq!(allocation.lifetime, 600);
assert!(as_success(AllocateResult::AuthRequired {
realm: "realm".to_string(),
nonce: "nonce".to_string(),
})
.is_none());
}
#[tokio::test]
async fn test_parse_error_response_short_error_code() {
use bytes::{BufMut, BytesMut};
let client = create_test_client([0x11; 12]).await;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(2);
attrs.put_u16(0x1234);
let result = client.parse_error_response(&attrs);
assert_turn_err_contains(result, "ErrorResponse");
}
#[tokio::test]
async fn test_parse_error_response_auth_required_missing_fields() {
use bytes::{BufMut, BytesMut};
let client = create_test_client([0x11; 12]).await;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(4);
attrs.put_u16(0);
attrs.put_u8(4);
attrs.put_u8(1);
let result = client.parse_error_response(&attrs);
assert_turn_err_contains(result, "401");
}
#[tokio::test]
async fn test_parse_refresh_response_too_short() {
let client = create_test_client([0x11; 12]).await;
let data = [0u8; 10];
let result = client.parse_refresh_response(&data);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_refresh_response_truncated_padding() {
use bytes::{BufMut, BytesMut};
let txn_id = [0x11u8; 12];
let client = create_test_client(txn_id).await;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_LIFETIME);
attrs.put_u16(1);
attrs.put_u8(1);
let mut msg = BytesMut::new();
msg.put_u16(REFRESH_RESPONSE);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let result = client.parse_refresh_response(&msg).unwrap();
assert_eq!(result, 600);
}
#[tokio::test]
async fn test_parse_data_indication_truncated_padding() {
use bytes::{BufMut, BytesMut};
let txn_id = [0x11u8; 12];
let client = create_test_client(txn_id).await;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_DATA);
attrs.put_u16(1);
attrs.put_u8(0xAB);
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let result = client.parse_data_indication(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_refresh_response_wrong_type() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(0x9999); msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
let client = create_test_client(txn_id).await;
let result = client.parse_refresh_response(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_refresh_response_truncated_attr() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(REFRESH_RESPONSE);
msg.put_u16(6); msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_u16(ATTR_LIFETIME);
msg.put_u16(4);
msg.put_u16(0x1234);
let client = create_test_client(txn_id).await;
let result = client.parse_refresh_response(&msg).unwrap();
assert_eq!(result, 600);
}
#[tokio::test]
async fn test_parse_refresh_response_unknown_attr_default() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(REFRESH_RESPONSE);
msg.put_u16(8);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_u16(ATTR_REALM);
msg.put_u16(4);
msg.put_u32(0x01020304);
let client = create_test_client(txn_id).await;
let result = client.parse_refresh_response(&msg).unwrap();
assert_eq!(result, 600);
}
#[tokio::test]
async fn test_parse_permission_response_wrong_type() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(0x9999); msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
let client = create_test_client(txn_id).await;
let result = client.parse_permission_response(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_permission_response_too_short() {
let client = create_test_client([0x11; 12]).await;
let msg = [0u8; 10];
let result = client.parse_permission_response(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_data_indication_success() {
let txn_id = [0x11u8; 12];
let peer_addr: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let data = b"test payload";
let indication = MockTurnServer::build_data_indication(&txn_id, peer_addr, data);
let client = create_test_client(txn_id).await;
let result = client.parse_data_indication(&indication);
assert!(result.is_ok());
let (addr, payload) = result.unwrap();
assert_eq!(addr, peer_addr);
assert_eq!(payload, data);
}
#[tokio::test]
async fn test_parse_data_indication_ignores_unknown_attr() {
let txn_id = [0x11u8; 12];
let peer_addr: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let data = b"payload";
let mut attrs = BytesMut::new();
attrs.put_u16(0x9999);
attrs.put_u16(4);
attrs.put_slice(&[1, 2, 3, 4]);
encode_xor_address(&mut attrs, ATTR_XOR_PEER_ADDRESS, peer_addr, &txn_id);
attrs.put_u16(ATTR_DATA);
attrs.put_u16(data.len() as u16);
attrs.put_slice(data);
pad_to_4_bytes(&mut attrs, data.len());
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let client = create_test_client(txn_id).await;
let result = client.parse_data_indication(&msg).unwrap();
assert_eq!(result.0, peer_addr);
assert_eq!(result.1, data);
}
#[tokio::test]
async fn test_parse_data_indication_too_short() {
let client = create_test_client([0x11; 12]).await;
let msg = [0u8; 10];
let result = client.parse_data_indication(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_data_indication_truncated_attr() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(6); msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_u16(ATTR_DATA);
msg.put_u16(4);
msg.put_u16(0x1234);
let client = create_test_client(txn_id).await;
let result = client.parse_data_indication(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_data_indication_wrong_type() {
let txn_id = [0x11u8; 12];
let mut msg = BytesMut::new();
msg.put_u16(ALLOCATE_RESPONSE); msg.put_u16(0);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
let client = create_test_client(txn_id).await;
let result = client.parse_data_indication(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_data_indication_missing_data() {
let txn_id = [0x11u8; 12];
let peer_addr: SocketAddr = "10.0.0.1:5000".parse().unwrap();
let mut attrs = BytesMut::new();
encode_xor_address(&mut attrs, ATTR_XOR_PEER_ADDRESS, peer_addr, &txn_id);
let mut msg = BytesMut::new();
msg.put_u16(DATA_INDICATION);
msg.put_u16(attrs.len() as u16);
msg.put_u32(MAGIC_COOKIE);
msg.put_slice(&txn_id);
msg.put_slice(&attrs);
let client = create_test_client(txn_id).await;
let result = client.parse_data_indication(&msg);
assert_turn_err_contains(result, "InvalidResponse");
}
#[tokio::test]
async fn test_parse_error_response_500() {
let txn_id = [0x11u8; 12];
let response = MockTurnServer::build_error_response(&txn_id, 500, "Server Error");
let client = create_test_client(txn_id).await;
let result = client.parse_error_response(&response[20..]);
assert_turn_err_contains(result, "500");
}
#[tokio::test]
async fn test_parse_error_response_auth_required() {
let txn_id = [0x11u8; 12];
let response = MockTurnServer::build_auth_required(&txn_id, "realm", "nonce");
let client = create_test_client(txn_id).await;
let result = client.parse_error_response(&response[20..]).unwrap();
let (realm, nonce) = as_auth_required(result).expect("Expected auth required result");
assert_eq!(realm, "realm");
assert_eq!(nonce, "nonce");
assert!(as_auth_required(AllocateResult::Success(TurnAllocation {
relayed_addr: "203.0.113.1:49152".parse().unwrap(),
mapped_addr: "192.0.2.1:49152".parse().unwrap(),
lifetime: 600,
realm: "realm".to_string(),
nonce: "nonce".to_string(),
}))
.is_none());
}
#[tokio::test]
async fn test_parse_error_response_truncated_attr() {
let client = create_test_client([0x11; 12]).await;
let mut attrs = BytesMut::new();
attrs.put_u16(ATTR_ERROR_CODE);
attrs.put_u16(10); attrs.put_u8(0x00);
let result = client.parse_error_response(&attrs);
assert_turn_err_contains(result, "code: 0");
}
}