use super::{Candidate, IceCredentials, IceEvent};
use crate::Component;
use std::{
cmp::min,
net::SocketAddr,
time::{Duration, Instant},
};
use stun_types::{
Class, Message, MessageBuilder, Method, TransactionId,
attributes::{
ErrorCode, Fingerprint, IceControlled, IceControlling, MessageIntegrity,
MessageIntegrityKey, Priority, UseCandidate, Username, XorMappedAddress,
},
};
pub(crate) struct StunConfig {
pub(crate) initial_rto: Duration,
pub(crate) max_retransmits: u32,
pub(crate) max_rto: Duration,
pub(crate) binding_refresh_interval: Duration,
}
impl StunConfig {
pub(crate) fn new() -> Self {
Self {
initial_rto: Duration::from_millis(250),
max_retransmits: 7,
max_rto: Duration::from_secs(3),
binding_refresh_interval: Duration::from_secs(20),
}
}
pub(crate) fn retransmit_delta(&self, attempts: u32) -> Duration {
let rto = Duration::from_millis(
(self.initial_rto.as_millis().checked_shl(attempts))
.unwrap_or(u128::from(u64::MAX))
.min(u128::from(u64::MAX)) as u64,
);
min(rto, self.max_rto)
}
}
pub(super) fn make_binding_request(
transaction_id: TransactionId,
local_credentials: &IceCredentials,
remote_credentials: &IceCredentials,
local_candidate: &Candidate,
is_controlling: bool,
control_tie_breaker: u64,
use_candidate: bool,
) -> Vec<u8> {
let mut stun_message = MessageBuilder::new(Class::Request, Method::Binding, transaction_id);
let username = format!("{}:{}", remote_credentials.ufrag, local_credentials.ufrag);
stun_message.add_attr(Username::new(&username));
stun_message.add_attr(Priority(local_candidate.priority));
if is_controlling {
stun_message.add_attr(IceControlling(control_tie_breaker));
} else {
stun_message.add_attr(IceControlled(control_tie_breaker));
}
if use_candidate {
stun_message.add_attr(UseCandidate);
}
stun_message.add_attr_with(
MessageIntegrity,
MessageIntegrityKey::new(&remote_credentials.pwd),
);
stun_message.add_attr(Fingerprint);
stun_message.finish()
}
pub(super) fn make_success_response(
transaction_id: TransactionId,
local_credentials: &IceCredentials,
source: SocketAddr,
) -> Vec<u8> {
let mut stun_message = MessageBuilder::new(Class::Success, Method::Binding, transaction_id);
stun_message.add_attr(XorMappedAddress(source));
stun_message.add_attr_with(
MessageIntegrity,
MessageIntegrityKey::new(&local_credentials.pwd),
);
stun_message.add_attr(Fingerprint);
stun_message.finish()
}
pub(super) fn make_role_error(
transaction_id: TransactionId,
local_credentials: &IceCredentials,
remote_credentials: &IceCredentials,
source: SocketAddr,
is_controlling: bool,
control_tie_breaker: u64,
) -> Vec<u8> {
let mut stun_message = MessageBuilder::new(Class::Error, Method::Binding, transaction_id);
let username = format!("{}:{}", local_credentials.ufrag, remote_credentials.ufrag);
stun_message.add_attr(Username::new(&username));
stun_message.add_attr(ErrorCode {
number: 487,
reason: "Role Conflict",
});
if is_controlling {
stun_message.add_attr(IceControlling(control_tie_breaker));
} else {
stun_message.add_attr(IceControlled(control_tie_breaker));
}
stun_message.add_attr(XorMappedAddress(source));
stun_message.add_attr_with(
MessageIntegrity,
MessageIntegrityKey::new(&remote_credentials.pwd),
);
stun_message.add_attr(Fingerprint);
stun_message.finish()
}
pub(crate) fn verify_integrity(
local_credentials: &IceCredentials,
remote_credentials: &IceCredentials,
stun_msg: &mut Message,
) -> bool {
let is_request = match stun_msg.class() {
Class::Request | Class::Indication => true,
Class::Success | Class::Error => false,
};
let key = if is_request {
&local_credentials.pwd
} else {
&remote_credentials.pwd
};
let passed_integrity_check = stun_msg
.attribute_with::<MessageIntegrity>(MessageIntegrityKey::new(key))
.is_some_and(|r| r.is_ok());
if !passed_integrity_check {
return false;
}
if is_request {
let expected_username = format!("{}:{}", local_credentials.ufrag, remote_credentials.ufrag);
let username = match stun_msg.attribute::<Username>() {
Some(Ok(username)) => username,
Some(Err(e)) => {
log::debug!("Failed to parse STUN username attribute, {e}");
return false;
}
None => {
log::debug!("STUN request is missing the USERNAME attribute");
return false;
}
};
if username.0 != expected_username {
return false;
}
}
true
}
pub(crate) struct StunServerBinding {
server: SocketAddr,
component: Component,
state: StunServerBindingState,
last_mapped_addr: Option<SocketAddr>,
}
enum StunServerBindingState {
Waiting,
InProgress {
transaction_id: TransactionId,
stun_request: Vec<u8>,
retransmit_at: Instant,
retransmits: u32,
},
WaitingForRefresh { refresh_at: Instant },
Failed,
}
impl StunServerBinding {
pub(crate) fn new(server: SocketAddr, component: Component) -> Self {
Self {
server,
component,
state: StunServerBindingState::Waiting,
last_mapped_addr: None,
}
}
pub(crate) fn component(&self) -> Component {
self.component
}
pub(crate) fn server(&self) -> SocketAddr {
self.server
}
pub(crate) fn is_completed(&self) -> bool {
self.last_mapped_addr.is_some() || matches!(self.state, StunServerBindingState::Failed)
}
pub(crate) fn timeout(&self, now: Instant) -> Option<Duration> {
match &self.state {
StunServerBindingState::Waiting => Some(Duration::ZERO),
StunServerBindingState::InProgress { retransmit_at, .. } => Some(
retransmit_at
.checked_duration_since(now)
.unwrap_or(Duration::ZERO),
),
StunServerBindingState::WaitingForRefresh { refresh_at, .. } => Some(
refresh_at
.checked_duration_since(now)
.unwrap_or(Duration::ZERO),
),
StunServerBindingState::Failed => None,
}
}
pub(crate) fn poll(
&mut self,
now: Instant,
stun_config: &StunConfig,
mut on_event: impl FnMut(IceEvent),
) {
match &mut self.state {
StunServerBindingState::Waiting => {
self.start_binding_request(now, stun_config, on_event)
}
StunServerBindingState::InProgress {
transaction_id: _,
stun_request,
retransmit_at,
retransmits,
} => {
if *retransmit_at > now {
return;
}
if *retransmits >= stun_config.max_retransmits {
self.state = StunServerBindingState::Failed;
self.last_mapped_addr = None;
return;
}
*retransmits += 1;
*retransmit_at += stun_config.retransmit_delta(*retransmits);
on_event(IceEvent::SendData {
component: self.component,
data: stun_request.clone(),
source: None,
target: self.server,
});
}
StunServerBindingState::WaitingForRefresh { refresh_at, .. } => {
if now >= *refresh_at {
self.start_binding_request(now, stun_config, on_event);
}
}
StunServerBindingState::Failed => {
}
}
}
fn start_binding_request(
&mut self,
now: Instant,
stun_config: &StunConfig,
mut on_event: impl FnMut(IceEvent),
) {
let transaction_id = TransactionId::random();
let mut builder = MessageBuilder::new(Class::Request, Method::Binding, transaction_id);
builder.add_attr(Fingerprint);
let stun_request = builder.finish();
on_event(IceEvent::SendData {
component: self.component,
data: stun_request.clone(),
source: None,
target: self.server,
});
self.state = StunServerBindingState::InProgress {
transaction_id,
stun_request,
retransmit_at: now + stun_config.retransmit_delta(0),
retransmits: 0,
};
}
pub(crate) fn wants_stun_response(&self, transaction_id: TransactionId) -> bool {
matches!(&self.state, StunServerBindingState::InProgress { transaction_id: tsx_id, .. } if transaction_id == *tsx_id)
}
pub(crate) fn receive_stun_response(
&mut self,
now: Instant,
stun_config: &StunConfig,
mut stun_msg: Message,
) -> Option<SocketAddr> {
let mapped = stun_msg.attribute::<XorMappedAddress>()?.ok()?;
self.state = StunServerBindingState::WaitingForRefresh {
refresh_at: now + stun_config.binding_refresh_interval,
};
self.last_mapped_addr = Some(mapped.0);
Some(mapped.0)
}
}