#[cfg(feature = "webrtc")]
pub mod webrtc_stun {
use std::net::{SocketAddr, UdpSocket};
use web_time::Duration;
pub fn parse_ice_server(uri: &str) -> Option<(String, SocketAddr)> {
let uri = uri.trim();
let (scheme, rest) = if let Some(stripped) = uri.strip_prefix("stun:") {
("stun", stripped)
} else if let Some(stripped) = uri.strip_prefix("stuns:") {
("stuns", stripped)
} else if let Some(stripped) = uri.strip_prefix("turn:") {
("turn", stripped)
} else if let Some(stripped) = uri.strip_prefix("turns:") {
("turns", stripped)
} else {
("stun", uri)
};
let rest = rest.trim_start_matches('/');
let (host, port) = if let Some(pos) = rest.rfind(':') {
let h = &rest[..pos];
let p = &rest[pos + 1..];
(h, p.parse::<u16>().ok()?)
} else {
(rest, 3478u16)
};
let addr = format!("{}:{}", host, port).parse::<SocketAddr>().ok()?;
Some((scheme.to_string(), addr))
}
pub fn stun_binding_request(
local_socket: &UdpSocket,
stun_server: SocketAddr,
timeout: Duration,
) -> Option<SocketAddr> {
use std::io::Cursor;
use stun::attributes::ATTR_XORMAPPED_ADDRESS;
use stun::message::{BINDING_REQUEST, Message};
use stun::xoraddr::XorMappedAddress;
let mut request = Message::new();
request.new_transaction_id().ok()?;
request.typ = BINDING_REQUEST;
let mut buf = vec![0u8; 1024];
let n = request.write_to(&mut &mut buf[..]).ok()?;
let req_bytes = &buf[..n];
local_socket.send_to(req_bytes, stun_server).ok()?;
local_socket.set_read_timeout(Some(timeout)).ok()?;
let mut resp_buf = [0u8; 1024];
let (n, from) = local_socket.recv_from(&mut resp_buf).ok()?;
if from != stun_server {
return None;
}
let mut response = Message::new();
let mut cursor = Cursor::new(&resp_buf[..n]);
response.read_from(&mut cursor).ok()?;
let mut xma = XorMappedAddress::default();
xma.get_from_as(&response, ATTR_XORMAPPED_ADDRESS).ok()?;
Some(SocketAddr::new(xma.ip, xma.port))
}
pub fn turn_allocate_request(
local_socket: &UdpSocket,
turn_server: SocketAddr,
timeout: Duration,
) -> Option<SocketAddr> {
use std::io::Cursor;
use stun::attributes::{ATTR_REQUESTED_TRANSPORT, ATTR_XOR_RELAYED_ADDRESS};
use stun::message::{CLASS_REQUEST, METHOD_ALLOCATE, Message, MessageType};
use stun::xoraddr::XorMappedAddress;
let mut request = Message::new();
request.new_transaction_id().ok()?;
request.typ = MessageType::new(METHOD_ALLOCATE, CLASS_REQUEST);
request.add(ATTR_REQUESTED_TRANSPORT, &[17u8, 0, 0, 0]);
let mut buf = vec![0u8; 1024];
let n = request.write_to(&mut &mut buf[..]).ok()?;
let req_bytes = &buf[..n];
local_socket.send_to(req_bytes, turn_server).ok()?;
local_socket.set_read_timeout(Some(timeout)).ok()?;
let mut resp_buf = [0u8; 1024];
let (n, from) = local_socket.recv_from(&mut resp_buf).ok()?;
if from != turn_server {
return None;
}
let mut response = Message::new();
let mut cursor = Cursor::new(&resp_buf[..n]);
response.read_from(&mut cursor).ok()?;
let mut xra = XorMappedAddress::default();
xra.get_from_as(&response, ATTR_XOR_RELAYED_ADDRESS).ok()?;
Some(SocketAddr::new(xra.ip, xra.port))
}
#[cfg(test)]
mod tests {
use super::parse_ice_server;
#[test]
fn test_parse_stun_uri() {
let (scheme, addr) = parse_ice_server("stun:1.2.3.4:19302").unwrap();
assert_eq!(scheme, "stun");
assert_eq!(addr.port(), 19302);
}
#[test]
fn test_parse_stun_default_port() {
let (scheme, addr) = parse_ice_server("stun:1.2.3.4").unwrap();
assert_eq!(scheme, "stun");
assert_eq!(addr.port(), 3478);
}
#[test]
fn test_parse_turn_uri() {
let (scheme, addr) = parse_ice_server("turn:5.6.7.8:3478").unwrap();
assert_eq!(scheme, "turn");
assert_eq!(addr.port(), 3478);
}
#[test]
fn test_parse_stuns_uri() {
let (scheme, _addr) = parse_ice_server("stuns:1.2.3.4:5349").unwrap();
assert_eq!(scheme, "stuns");
}
#[test]
fn test_parse_turns_uri() {
let (scheme, _addr) = parse_ice_server("turns:1.2.3.4:5349").unwrap();
assert_eq!(scheme, "turns");
}
#[test]
fn test_parse_no_scheme_defaults_stun() {
let (scheme, addr) = parse_ice_server("1.2.3.4:3478").unwrap();
assert_eq!(scheme, "stun");
assert_eq!(addr.port(), 3478);
}
#[test]
fn test_parse_invalid_port() {
assert!(parse_ice_server("stun:1.2.3.4:abc").is_none());
}
#[test]
fn test_parse_with_leading_slash() {
let (_scheme, addr) = parse_ice_server("stun://1.2.3.4:3478").unwrap();
assert_eq!(addr.port(), 3478);
}
}
}
#[cfg(not(feature = "webrtc"))]
pub mod webrtc_stun {
use std::net::SocketAddr;
pub fn parse_ice_server(_uri: &str) -> Option<(String, SocketAddr)> {
None
}
pub fn stun_binding_request(
_local_socket: &std::net::UdpSocket,
_stun_server: SocketAddr,
_timeout: web_time::Duration,
) -> Option<SocketAddr> {
None
}
pub fn turn_allocate_request(
_local_socket: &std::net::UdpSocket,
_turn_server: SocketAddr,
_timeout: web_time::Duration,
) -> Option<SocketAddr> {
None
}
}
#[cfg(all(test, not(feature = "webrtc")))]
mod tests {
use super::webrtc_stun::parse_ice_server;
#[test]
fn test_parse_stun_uri_no_webrtc_returns_none() {
assert!(parse_ice_server("stun:1.2.3.4:19302").is_none());
}
}