Skip to main content

rustrtc/transports/ice/
mod.rs

1pub mod conn;
2#[cfg(feature = "std")] // mDNS is excluded from the embedded target
3pub mod mdns;
4#[cfg(feature = "std")] // ICE-TCP is excluded from the embedded target
5pub mod shared_tcp;
6
7/// Placeholder registration (ICE-TCP excluded; never constructed).
8#[cfg(not(feature = "std"))]
9#[derive(Debug, Clone)]
10pub struct SharedTcpRegistration;
11pub mod shared_udp;
12pub mod stun;
13#[cfg(all(test, feature = "std"))]
14mod tests;
15pub mod turn;
16#[cfg(feature = "std")] // UPnP is excluded from the embedded target
17pub mod upnp;
18
19// Re-export UPnP types
20#[cfg(feature = "std")]
21pub use upnp::{
22    DEFAULT_LEASE_DURATION, DEFAULT_UPNP_DISCOVERY_TIMEOUT, MAX_LEASE_DURATION, MIN_LEASE_DURATION,
23    UpnpPortMapper,
24};
25
26use crate::config::{BufferDropStrategy, IceServer, IceTransportPolicy, RtcConfiguration};
27use crate::platform::atomic64::AtomicU64;
28use crate::prelude::*;
29use crate::transports::ice::turn::{TurnClient, TurnCredentials};
30use crate::transports::{PacketReceiver, get_local_ip};
31use alloc::collections::{BTreeMap, VecDeque};
32use alloc::sync::Arc;
33use bytes::Bytes;
34use core::net::{IpAddr, SocketAddr};
35use core::sync::atomic::AtomicU32;
36use core::sync::atomic::Ordering;
37use futures::future::BoxFuture;
38use futures::stream::{FuturesUnordered, StreamExt};
39#[cfg(feature = "std")]
40use std::io::ErrorKind;
41
42#[cfg(not(feature = "std"))]
43use crate::platform::time::Instant;
44use core::time::Duration;
45#[cfg(feature = "std")]
46use std::time::Instant;
47
48use crate::errors::{RtcError, RtcResult};
49#[cfg(feature = "std")]
50use crate::platform::net::UdpSocket as NetUdpSocket;
51#[cfg(feature = "std")]
52use tokio::net::UdpSocket;
53#[cfg(feature = "std")]
54use tokio::net::{TcpListener, TcpStream};
55#[cfg(not(feature = "std"))]
56/// Placeholder standing in for the tokio UDP socket on embedded targets:
57/// direct-UDP gathering is a std-only path (the target uses Platform
58/// sockets via the embedder's adapter). Never constructed.
59#[derive(Debug, Clone)]
60pub struct UdpSocket;
61
62#[cfg(not(feature = "std"))]
63impl UdpSocket {
64    pub async fn bind(_addr: SocketAddr) -> RtcResult<Self> {
65        unimplemented!("direct UDP sockets are std-only; use Platform sockets (WP3)")
66    }
67    pub fn local_addr(&self) -> RtcResult<SocketAddr> {
68        unimplemented!("direct UDP sockets are std-only")
69    }
70    pub async fn send_to(&self, _: &[u8], _: SocketAddr) -> RtcResult<usize> {
71        unimplemented!("direct UDP sockets are std-only")
72    }
73    pub async fn recv_from(&self, _: &mut [u8]) -> RtcResult<(usize, SocketAddr)> {
74        unimplemented!("direct UDP sockets are std-only")
75    }
76    pub fn try_send_to(&self, _: &[u8], _: SocketAddr) -> RtcResult<usize> {
77        unimplemented!("direct UDP sockets are std-only")
78    }
79    pub async fn writable(&self) -> RtcResult<()> {
80        unimplemented!("direct UDP sockets are std-only")
81    }
82    pub fn diag(&self) -> SocketAddr {
83        unimplemented!("direct UDP sockets are std-only")
84    }
85}
86
87/// Placeholder for the tokio TcpStream on embedded targets: ICE-TCP is
88/// excluded there. Never constructed.
89#[cfg(not(feature = "std"))]
90#[derive(Debug, Clone)]
91pub struct TcpStream;
92
93#[cfg(not(feature = "std"))]
94impl TcpStream {
95    pub async fn connect(_addr: SocketAddr) -> RtcResult<Self> {
96        unimplemented!("ICE-TCP is excluded from the embedded target")
97    }
98    pub fn local_addr(&self) -> RtcResult<SocketAddr> {
99        unimplemented!("ICE-TCP is excluded from the embedded target")
100    }
101    pub fn set_nodelay(&self, _: bool) -> RtcResult<()> {
102        Ok(())
103    }
104    pub async fn write_all(&self, _: &[u8]) -> RtcResult<()> {
105        unimplemented!("ICE-TCP is excluded from the embedded target")
106    }
107    pub async fn read_exact(&self, _: &mut [u8]) -> RtcResult<()> {
108        unimplemented!("ICE-TCP is excluded from the embedded target")
109    }
110    pub fn into_split(self) -> (TcpReadHalf, TcpWriteHalf) {
111        unimplemented!("ICE-TCP is excluded from the embedded target")
112    }
113}
114
115/// Placeholder for the tokio TcpListener on embedded targets.
116#[cfg(not(feature = "std"))]
117#[derive(Debug, Clone)]
118pub struct TcpListener;
119
120#[cfg(not(feature = "std"))]
121impl TcpListener {
122    pub async fn bind(_addr: SocketAddr) -> RtcResult<Self> {
123        unimplemented!("ICE-TCP is excluded from the embedded target")
124    }
125    pub fn local_addr(&self) -> RtcResult<SocketAddr> {
126        unimplemented!("ICE-TCP is excluded from the embedded target")
127    }
128}
129
130/// Placeholder halves (ICE-TCP excluded).
131#[cfg(not(feature = "std"))]
132pub type TcpReadHalf = TcpStream;
133#[cfg(not(feature = "std"))]
134pub type TcpWriteHalf = TcpStream;
135
136/// Placeholder for the UPnP port mapper (UPnP excluded from the embedded
137/// target). Never constructed.
138#[cfg(not(feature = "std"))]
139#[derive(Debug, Clone)]
140pub struct UpnpPortMapper;
141
142#[cfg(not(feature = "std"))]
143impl UpnpPortMapper {
144    pub fn with_lease_duration(_local_addr: SocketAddr, _lease: u32) -> Self {
145        unimplemented!("UPnP is excluded from the embedded target")
146    }
147    pub async fn discover(&mut self) -> RtcResult<()> {
148        unimplemented!("UPnP is excluded from the embedded target")
149    }
150    pub async fn add_mapping(&mut self, _port: u16) -> RtcResult<SocketAddr> {
151        unimplemented!("UPnP is excluded from the embedded target")
152    }
153    pub async fn renew_all_stale(&self) -> RtcResult<()> {
154        unimplemented!("UPnP is excluded from the embedded target")
155    }
156    pub async fn discover_with_timeout(&mut self, _: core::time::Duration) -> RtcResult<()> {
157        unimplemented!("UPnP is excluded from the embedded target")
158    }
159    pub async fn add_mapping_random_port(&mut self, _: u16) -> RtcResult<u16> {
160        unimplemented!("UPnP is excluded from the embedded target")
161    }
162    pub async fn cleanup(&self) -> RtcResult<()> {
163        Ok(())
164    }
165    pub async fn get_external_ip(&self) -> RtcResult<core::net::Ipv4Addr> {
166        unimplemented!("UPnP is excluded from the embedded target")
167    }
168}
169use crate::platform::sync::{AsyncMutex as Mutex, broadcast, mpsc, oneshot, watch};
170use crate::platform::time::with_timeout;
171use tracing::{debug, error, instrument, trace, warn};
172
173#[cfg(any(test, feature = "simulator"))]
174use self::stun::random_u32;
175use self::stun::{
176    StunAttribute, StunClass, StunDecoded, StunMessage, StunMethod, random_bytes, random_u64,
177};
178
179pub(crate) const MAX_STUN_MESSAGE: usize = 1500;
180#[cfg(any(test, feature = "simulator"))]
181static PACKET_LOSS_RATE: AtomicU32 = AtomicU32::new(u32::MAX);
182
183pub(crate) fn should_drop_packet() -> bool {
184    #[cfg(not(any(test, feature = "simulator")))]
185    return false;
186
187    #[cfg(any(test, feature = "simulator"))]
188    {
189        let mut rate = PACKET_LOSS_RATE.load(Ordering::Relaxed);
190        if rate == u32::MAX {
191            #[cfg(feature = "std")]
192            {
193                rate = std::env::var("RUSTRTC_PACKET_LOSS")
194                    .ok()
195                    .and_then(|s| s.parse::<f64>().ok())
196                    .map(|f| (f * 100.0) as u32)
197                    .unwrap_or(0);
198            }
199            #[cfg(not(feature = "std"))]
200            {
201                rate = 0;
202            }
203            PACKET_LOSS_RATE.store(rate, Ordering::Relaxed);
204        }
205
206        if rate == 0 {
207            return false;
208        }
209
210        let rand_val = random_u32() % 10000;
211        let drop = rand_val < rate;
212        if drop {
213            trace!("SIMULATOR: Dropping packet (rate={}%)", rate as f64 / 100.0);
214        }
215        drop
216    }
217}
218
219/// Simulator hook: delay STUN Binding responses so a nominally fast path can
220/// be made slower than a lower-priority one, reproducing the ICE nomination
221/// race seen across cascaded NAT (the controlling side picked a fast
222/// srflx/relay pair while the controlled side selected the host path).
223///
224/// `RUSTRTC_STUN_RESPOND_DELAY_MS=<ms>` delays every response sent from a
225/// direct (non-TURN) socket. Only used by tests.
226#[cfg(any(test, feature = "simulator"))]
227async fn simulate_stun_respond_delay(sender: &IceSocketWrapper) {
228    #[cfg(not(feature = "std"))]
229    {
230        // No env vars on the embedded target: the simulator hook is a no-op.
231        let _ = sender;
232    }
233    #[cfg(feature = "std")]
234    {
235        let spec = match std::env::var("RUSTRTC_STUN_RESPOND_DELAY_MS").ok() {
236            Some(s) => s,
237            None => return,
238        };
239        let Ok(ms) = spec.trim().parse::<u64>() else {
240            return;
241        };
242        if ms == 0 {
243            return;
244        }
245        let is_relayed_or_tcp = matches!(sender, IceSocketWrapper::Turn(_, _)) || matches!(
246            sender,
247            IceSocketWrapper::TcpListener(_) | IceSocketWrapper::TcpStream(_, _, _)
248        );
249        if is_relayed_or_tcp {
250            return;
251        }
252        trace!("SIMULATOR: delaying STUN response by {}ms", ms);
253        crate::platform::task::sleep(Duration::from_millis(ms)).await;
254    }
255}
256
257/// Statistics for monitoring buffer behavior
258#[derive(Debug)]
259struct BufferStats {
260    pub packets_received: AtomicU64,
261    pub packets_dropped: AtomicU64,
262    pub current_size: AtomicU32,
263    pub peak_size: AtomicU32,
264    pub last_log_time: crate::platform::sync::Mutex<Instant>,
265}
266
267impl Default for BufferStats {
268    fn default() -> Self {
269        Self {
270            packets_received: AtomicU64::new(0),
271            packets_dropped: AtomicU64::new(0),
272            current_size: AtomicU32::new(0),
273            peak_size: AtomicU32::new(0),
274            last_log_time: crate::platform::sync::Mutex::new(Instant::now()),
275        }
276    }
277}
278
279#[derive(Debug)]
280enum IceCommand {
281    StartGathering,
282    RunChecks,
283}
284
285#[derive(Debug, Clone)]
286pub struct IceTransport {
287    inner: Arc<IceTransportInner>,
288}
289
290pub(crate) struct IceTransportInner {
291    state: watch::Sender<IceTransportState>,
292    _state_rx_keeper: watch::Receiver<IceTransportState>,
293    gathering_state: watch::Sender<IceGathererState>,
294    /// Keeper receiver for the gathering_state watch channel. Without a live
295    /// receiver, `watch::Sender::send()` with zero receivers returns Err and
296    /// never stores the value — a late subscriber would miss the `Complete`
297    /// update and `wait_for_gathering_complete()` would hang forever. Every
298    /// other watch channel in this struct holds a keeper receiver for exactly
299    /// this reason.
300    _gathering_state_rx_keeper: watch::Receiver<IceGathererState>,
301    role: crate::platform::sync::Mutex<IceRole>,
302    selected_pair: crate::platform::sync::Mutex<Option<IceCandidatePair>>,
303    local_candidates: Mutex<Vec<IceCandidate>>,
304    remote_candidates: crate::platform::sync::Mutex<Vec<IceCandidate>>,
305    gather_state: crate::platform::sync::Mutex<IceGathererState>,
306    config: RtcConfiguration,
307    gatherer: IceGatherer,
308    local_parameters: crate::platform::sync::Mutex<IceParameters>,
309    remote_parameters: crate::platform::sync::Mutex<Option<IceParameters>>,
310    pending_transactions:
311        crate::platform::sync::Mutex<BTreeMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
312    data_receiver: crate::platform::sync::Mutex<Option<Arc<dyn PacketReceiver>>>,
313    /// Ring buffer for packets when no receiver is registered yet.
314    /// Uses VecDeque for efficient pop_front removal.
315    buffered_packets: crate::platform::sync::Mutex<VecDeque<(Vec<u8>, SocketAddr)>>,
316    /// Statistics for monitoring buffer behavior
317    buffer_stats: Arc<BufferStats>,
318    selected_socket: watch::Sender<Option<IceSocketWrapper>>,
319    _socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
320    selected_rtcp_socket: watch::Sender<Option<IceSocketWrapper>>,
321    _rtcp_socket_rx_keeper: watch::Receiver<Option<IceSocketWrapper>>,
322    selected_pair_notifier: watch::Sender<Option<IceCandidatePair>>,
323    _selected_pair_rx_keeper: watch::Receiver<Option<IceCandidatePair>>,
324    /// Reference epoch used to interpret `last_received_nanos`.
325    created_at: Instant,
326    /// Nanoseconds since `created_at` when the most recent packet arrived.
327    /// Stored atomically so the per-packet receive path records the timestamp
328    /// without taking a mutex.
329    last_received_nanos: AtomicU64,
330    candidate_tx: broadcast::Sender<IceCandidate>,
331    cmd_tx: mpsc::UnboundedSender<IceCommand>,
332    checking_pairs: Mutex<alloc::collections::BTreeSet<(SocketAddr, SocketAddr)>>,
333    /// Signals when the controlling-side nomination is complete.
334    /// `true` = nomination succeeded, `false` = nomination failed (but ICE is still connected).
335    /// Controlled side immediately sends `true` (no nomination to do).
336    nomination_complete: watch::Sender<Option<bool>>,
337    _nomination_complete_rx: watch::Receiver<Option<bool>>,
338    /// Set while a locally-initiated ICE restart is in flight (between
339    /// `restart()` and the next `start()` with the peer's answer). Prevents a
340    /// changed remote ufrag/pwd in that answer from being misread as a
341    /// remote-initiated restart (which would reset ICE a second time).
342    restart_requested: core::sync::atomic::AtomicBool,
343    /// Monotonic counter bumped for every new controlled-side nomination and on
344    /// every ICE restart. A deferred path-verification task only commits its
345    /// pair while its captured generation is still current, so a stale
346    /// nomination whose verification finishes late cannot switch media back to
347    /// a superseded path (RFC 8445 §8.1.1).
348    /// no_std: backed by the 32-bit atomic64 shim.
349    nomination_generation: crate::platform::atomic64::AtomicU64,
350    /// mDNS hostname advertising our host candidates (`enable_mdns`).
351    mdns_hostname: Option<String>,
352    /// Guards against overlapping `run_turn_refresh` invocations: the refresh
353    /// timer tick skips when a previous refresh is still in flight instead of
354    /// cancelling it (which used to orphan pending transactions).
355    turn_refresh_in_progress: core::sync::atomic::AtomicBool,
356    /// Guards against overlapping UPnP mapping refreshes (same skip-on-in-flight
357    /// pattern as `turn_refresh_in_progress`).
358    upnp_refresh_in_progress: core::sync::atomic::AtomicBool,
359}
360
361impl core::fmt::Debug for IceTransportInner {
362    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
363        f.debug_struct("IceTransportInner")
364            .field("state", &self.state)
365            .field("role", &self.role)
366            .field("selected_pair", &self.selected_pair)
367            .field("local_candidates", &self.local_candidates)
368            .field("remote_candidates", &self.remote_candidates)
369            .field("gather_state", &self.gather_state)
370            .field("config", &self.config)
371            .field("gatherer", &self.gatherer)
372            .field("local_parameters", &self.local_parameters)
373            .field("remote_parameters", &self.remote_parameters)
374            .field("pending_transactions", &self.pending_transactions)
375            .field("data_receiver", &"PacketReceiver")
376            .field("buffered_packets", &self.buffered_packets.lock().len())
377            .field("buffer_stats", &self.buffer_stats)
378            .field("selected_socket", &self.selected_socket)
379            .field("selected_rtcp_socket", &self.selected_rtcp_socket)
380            .field("selected_pair_notifier", &self.selected_pair_notifier)
381            .field("candidate_tx", &self.candidate_tx)
382            .field("cmd_tx", &self.cmd_tx)
383            .field("nomination_complete", &self.nomination_complete)
384            .finish()
385    }
386}
387
388/// Collect the local addresses to advertise in mDNS answers and start a
389/// responder for the transport's obfuscated hostname.
390#[cfg(feature = "std")]
391fn start_mdns_responder(
392    inner: &Arc<IceTransportInner>,
393) -> anyhow::Result<crate::transports::ice::mdns::MdnsResponder> {
394    let mut addresses: Vec<core::net::IpAddr> = Vec::new();
395    if let Some(bind) = &inner.config.bind_ip
396        && let Ok(ip) = bind.parse::<core::net::IpAddr>()
397    {
398        addresses.push(ip);
399    }
400    #[cfg(feature = "std")]
401    use local_ip_address::list_afinet_netifas;
402    if let Ok(interfaces) = list_afinet_netifas() {
403        for (_name, addr) in interfaces {
404            if !addr.is_loopback() && !addresses.contains(&addr) {
405                addresses.push(addr);
406            }
407        }
408    }
409    let hostname = inner
410        .mdns_hostname
411        .clone()
412        .unwrap_or_else(crate::transports::ice::mdns::MdnsResponder::generate_hostname);
413    crate::transports::ice::mdns::MdnsResponder::start(hostname, addresses)
414}
415
416struct IceTransportRunner {
417    inner: Arc<IceTransportInner>,
418    socket_rx: mpsc::UnboundedReceiver<IceSocketWrapper>,
419    candidate_rx: broadcast::Receiver<IceCandidate>,
420    cmd_rx: mpsc::UnboundedReceiver<IceCommand>,
421    state_rx: watch::Receiver<IceTransportState>,
422}
423
424impl IceTransportRunner {
425    async fn run(mut self) {
426
427        // mDNS responder lifetime: started when `enable_mdns` is set, stopped
428        // when the runner loop exits (the guard's Drop signals the task).
429        #[cfg(feature = "std")]
430        let _mdns: Option<crate::transports::ice::mdns::MdnsResponder> =
431            if self.inner.config.enable_mdns {
432                match start_mdns_responder(&self.inner) {
433                    Ok(responder) => {
434                        debug!("mDNS responder started for {}", responder.hostname());
435                        Some(responder)
436                    }
437                    Err(e) => {
438                        debug!("mDNS responder failed to start: {}", e);
439                        None
440                    }
441                }
442            } else {
443                None
444            };
445        let mut interval =
446            crate::platform::task::interval_after(Duration::from_secs(1), Duration::from_secs(1));
447        // TURN refresh interval. Kept well under both the 300 s permission
448        // timeout AND typical UDP NAT mapping idle timeouts (~30 s on many
449        // carrier/CGNAT deployments): each Refresh is bidirectional traffic
450        // on the client<->TURN-server 5-tuple, so this also keeps that NAT
451        // mapping warm for relays that are gathered but not selected (and
452        // therefore carry no media ChannelData). 25 s is safely under all
453        // those budgets while staying cheap (tiny authenticated packets).
454        let mut turn_refresh_interval =
455            crate::platform::task::interval_after(Duration::from_secs(25), Duration::from_secs(25));
456        // UPnP mapping refresh interval. Long-lived sessions (> 1 hour) outlive
457        // the default router lease (3600s); re-issuing AddPortMapping keeps the
458        // mapping alive so inbound P2P traffic isn't lost mid-call.
459        let mut upnp_refresh_interval = crate::platform::task::interval_after(
460            self.inner.config.upnp_refresh_interval,
461            self.inner.config.upnp_refresh_interval,
462        );
463        let mut read_futures: FuturesUnordered<BoxFuture<'static, ()>> = FuturesUnordered::new();
464        let mut gathering_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
465        let mut turn_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
466        let mut upnp_refresh_future: BoxFuture<'static, ()> = Box::pin(futures::future::pending());
467
468        loop {
469            // Backend-agnostic 11-arm race (replaces `tokio::select!`):
470            // every arm is polled once per wake in a fixed order; the first
471            // `Ready` wins. `Some(x) = rx.recv()` arms keep tokio's
472            // disabled-branch semantics through `*_open` flags — a closed
473            // channel stops polling that arm instead of spinning.
474            enum GatherArm {
475                State(Result<(), watch::RecvError>),
476                Socket(IceSocketWrapper),
477                Candidate(Result<(), broadcast::RecvError>),
478                Cmd(IceCommand),
479                Keepalive,
480                TurnRefreshTick,
481                TurnRefreshDone,
482                UpnpTick,
483                UpnpDone,
484                ReadLoopDone,
485                GatheringDone,
486            }
487            let mut socket_rx_open = true;
488            let mut cmd_rx_open = true;
489
490            let arm = core::future::poll_fn(|cx| {
491                use core::task::Poll;
492                let mut state_fut = core::pin::pin!(self.state_rx.changed());
493                let mut socket_fut = core::pin::pin!(self.socket_rx.recv());
494                let mut cand_fut = core::pin::pin!(self.candidate_rx.recv());
495                let mut cmd_fut = core::pin::pin!(self.cmd_rx.recv());
496                let mut ka_fut = core::pin::pin!(interval.tick());
497                let mut turn_tick_fut = core::pin::pin!(turn_refresh_interval.tick());
498                let mut upnp_tick_fut = core::pin::pin!(upnp_refresh_interval.tick());
499                let mut read_next_fut = core::pin::pin!(read_futures.next());
500                loop {
501                    if let Poll::Ready(res) = state_fut.as_mut().poll(cx) {
502                        return Poll::Ready(GatherArm::State(res));
503                    }
504                    if socket_rx_open && let Poll::Ready(v) = socket_fut.as_mut().poll(cx) {
505                        match v {
506                            Some(socket) => return Poll::Ready(GatherArm::Socket(socket)),
507                            None => socket_rx_open = false,
508                        }
509                    }
510                    if let Poll::Ready(res) = cand_fut.as_mut().poll(cx) {
511                        return Poll::Ready(GatherArm::Candidate(res.map(|_| ())));
512                    }
513                    if cmd_rx_open && let Poll::Ready(v) = cmd_fut.as_mut().poll(cx) {
514                        match v {
515                            Some(cmd) => return Poll::Ready(GatherArm::Cmd(cmd)),
516                            None => cmd_rx_open = false,
517                        }
518                    }
519                    if ka_fut.as_mut().poll(cx).is_ready() {
520                        return Poll::Ready(GatherArm::Keepalive);
521                    }
522                    if turn_tick_fut.as_mut().poll(cx).is_ready() {
523                        return Poll::Ready(GatherArm::TurnRefreshTick);
524                    }
525                    if core::pin::Pin::new(&mut turn_refresh_future)
526                        .poll(cx)
527                        .is_ready()
528                    {
529                        return Poll::Ready(GatherArm::TurnRefreshDone);
530                    }
531                    if upnp_tick_fut.as_mut().poll(cx).is_ready() {
532                        return Poll::Ready(GatherArm::UpnpTick);
533                    }
534                    if core::pin::Pin::new(&mut upnp_refresh_future)
535                        .poll(cx)
536                        .is_ready()
537                    {
538                        return Poll::Ready(GatherArm::UpnpDone);
539                    }
540                    if let Poll::Ready(Some(_)) = read_next_fut.as_mut().poll(cx) {
541                        return Poll::Ready(GatherArm::ReadLoopDone);
542                    }
543                    if core::pin::Pin::new(&mut gathering_future)
544                        .poll(cx)
545                        .is_ready()
546                    {
547                        return Poll::Ready(GatherArm::GatheringDone);
548                    }
549                    return Poll::Pending;
550                }
551            })
552            .await;
553            match arm {
554                GatherArm::State(res) => {
555                    if res.is_err() {
556                        break;
557                    }
558                    if matches!(
559                        *self.state_rx.borrow(),
560                        IceTransportState::Closed | IceTransportState::Failed
561                    ) {
562                        break;
563                    }
564                }
565                GatherArm::Socket(socket) => {
566                    match socket {
567                        IceSocketWrapper::Udp(s) => {
568                            // std: the tokio socket's dedicated read loop;
569                            // no_std: Udp is a placeholder, never constructed.
570                            #[cfg(feature = "std")]
571                            {
572                                read_futures
573                                    .push(Box::pin(Self::run_udp_read_loop(s, self.inner.clone())));
574                            }
575                            #[cfg(not(feature = "std"))]
576                            {
577                                let _ = s;
578                            }
579                        }
580                        IceSocketWrapper::SharedUdp(handle) => {
581                            read_futures.push(Box::pin(Self::run_shared_udp_read_loop(
582                                handle,
583                                self.inner.clone(),
584                            )));
585                        }
586                        #[cfg(feature = "std")]
587                        IceSocketWrapper::TcpListener(l) => {
588                            read_futures
589                                .push(Box::pin(Self::run_tcp_listen_loop(l, self.inner.clone())));
590                        }
591                        #[cfg(feature = "std")]
592                        IceSocketWrapper::TcpStream(read, write, peer) => {
593                            read_futures.push(Box::pin(Self::run_tcp_read_loop(
594                                read,
595                                write,
596                                peer,
597                                self.inner.clone(),
598                            )));
599                        }
600                        IceSocketWrapper::Platform(s) => {
601                            read_futures.push(Box::pin(Self::run_platform_read_loop(
602                                s,
603                                self.inner.clone(),
604                            )));
605                        }
606                        IceSocketWrapper::Turn(c, addr) => {
607                            read_futures.push(Box::pin(Self::run_turn_read_loop(
608                                c,
609                                addr,
610                                self.inner.clone(),
611                            )));
612                        }
613                    }
614                }
615                GatherArm::Candidate(res) => match res {                    Ok(_) => {
616                        let inner = self.inner.clone();
617                        read_futures.push(Box::pin(async move {
618                            perform_connectivity_checks_async(inner).await;
619                        }));
620                    }
621                    Err(broadcast::RecvError::Closed) => break,
622                    Err(broadcast::RecvError::Lagged(_)) => continue,
623                }
624                GatherArm::Cmd(cmd) => {
625
626
627                    trace!("Runner received command: {:?}", cmd);
628                    match cmd {
629                        IceCommand::StartGathering => {
630                            let inner = self.inner.clone();
631                            gathering_future = Box::pin(async move {
632                                if let Err(e) = inner.gatherer.gather().await {
633                                    debug!("Gathering failed: {}", e);
634                                }
635                                {
636                                    let mut buffer = inner.local_candidates.lock().await;
637                                    *buffer = inner.gatherer.local_candidates();
638                                }
639                                *inner.gather_state.lock() = IceGathererState::Complete;
640                                let _ = inner.gathering_state.send(IceGathererState::Complete);
641                            });
642                        }
643                        IceCommand::RunChecks => {
644                            let inner = self.inner.clone();
645                            // Spawn connectivity checks in a separate task so they don't
646                            // block the runner's event loop. This is critical for TCP
647                            // candidates: the check may block on TcpStream::connect while
648                            // the runner still needs to process pending socket_rx messages
649                            // (e.g. TcpListener accept loops) to complete the connection.
650                            let rt_handle = inner.config.runtime_handle.clone();
651                            crate::spawn_rtc(
652                                rt_handle.as_ref(),
653                                tracing::Span::current(),
654                                async move {
655                                    perform_connectivity_checks_async(inner).await;
656                                },
657                            );
658                        }
659                    }
660                }
661                GatherArm::Keepalive => {
662                    if let Some(f) = Self::run_keepalive_tick(&self.inner).await {
663                        read_futures.push(f);
664                    }
665                }
666                GatherArm::TurnRefreshTick => {
667                    // Only start a new refresh if the previous one has
668                    // completed. If still running (e.g. server slow), skip
669                    // this tick rather than reassigning the future, which
670                    // would cancel the in-flight refresh and orphan its
671                    // pending transactions. The in-progress flag is cleared
672                    // by `run_turn_refresh` on every exit path.
673                    if !self
674                        .inner
675                        .turn_refresh_in_progress
676                        .load(core::sync::atomic::Ordering::SeqCst)
677                    {
678                        let inner = self.inner.clone();
679                        turn_refresh_future = Box::pin(async move {
680                            Self::run_turn_refresh(&inner).await;
681                        });
682                    }
683                }
684                GatherArm::TurnRefreshDone => {
685                    turn_refresh_future = Box::pin(futures::future::pending());
686                }
687                GatherArm::UpnpTick => {
688                    // UPnP SOAP calls can be slow (hundreds of ms). Run them in
689                    // a detached future so the runner's 1s keepalive tick is
690                    // never blocked. Guard with an in-progress flag so slow
691                    // routers don't pile up overlapping refreshes.
692                    if !self
693                        .inner
694                        .upnp_refresh_in_progress
695                        .swap(true, core::sync::atomic::Ordering::SeqCst)
696                    {
697                        let inner = self.inner.clone();
698                        upnp_refresh_future = Box::pin(async move {
699                            #[cfg(feature = "std")]
700                            inner.gatherer.renew_upnp_mappings().await;
701                            #[cfg(not(feature = "std"))]
702                            {
703                                let _ = &inner;
704                            }
705                            inner
706                                .upnp_refresh_in_progress
707                                .store(false, core::sync::atomic::Ordering::SeqCst);
708                        });
709                    }
710                }
711                GatherArm::UpnpDone => {
712                    upnp_refresh_future = Box::pin(futures::future::pending());
713                }
714                GatherArm::ReadLoopDone => {
715                    // Read loop finished
716                }
717                GatherArm::GatheringDone => {
718                    gathering_future = Box::pin(futures::future::pending());
719                }
720            }
721        }
722    }
723
724    #[cfg(feature = "std")]
725    async fn run_udp_read_loop(socket: Arc<UdpSocket>, inner: Arc<IceTransportInner>) {
726        let mut buf = [0u8; 1500];
727        let mut marshal_buf = Vec::with_capacity(1500);
728        let mut state_rx = inner.state.subscribe();
729        let sender = IceSocketWrapper::Udp(socket.clone());
730        trace!("Read loop started for {:?}", socket.local_addr());
731        loop {
732            let which = {
733                let mut readable_fut = core::pin::pin!(socket.readable());
734                let mut state_fut = core::pin::pin!(state_rx.changed());
735                crate::platform::select::select2(&mut readable_fut, &mut state_fut).await
736            };
737            match which {
738                crate::platform::select::Either::A(res) => {
739                    if let Err(e) = res {
740                        debug!("Socket readable wait error: {}", e);
741                        break;
742                    }
743
744                    loop {
745                        let (len, addr) = match socket.try_recv_from(&mut buf) {
746                            Ok(v) => v,
747                            Err(e) if e.kind() == ErrorKind::WouldBlock => {
748                                break;
749                            }
750                            Err(e) if e.kind() == ErrorKind::ConnectionReset => {
751                                // Windows surfaces the ICMP "Port Unreachable" reply to an
752                                // earlier send as WSAECONNRESET (os error 10054) on the next
753                                // recv. UDP is connectionless, so this is not a fatal socket
754                                // error — return to the outer select and keep serving this
755                                // socket (same class of fix as tokio-rs/tokio#2017 and
756                                // aws/s2n-quic#1448; matches the tolerant handling already
757                                // used by the shared-UDP recv loop).
758                                debug!("Socket recv error (connection reset, continuing): {}", e);
759                                break;
760                            }
761                            Err(e) => {
762                                debug!("Socket recv error: {}", e);
763                                return;
764                            }
765                        };
766
767                        let packet = &buf[..len];
768                        if len > 0 {
769                            handle_packet(
770                                packet,
771                                addr,
772                                inner.clone(),
773                                sender.clone(),
774                                &mut marshal_buf,
775                            )
776                            .await;
777                        }
778                    }
779                }
780                crate::platform::select::Either::B(res) => {
781                    if res.is_err()
782                        || matches!(
783                            *state_rx.borrow(),
784                            IceTransportState::Closed | IceTransportState::Failed
785                        )
786                    {
787                        // routine teardown; one line per read loop is noisy at debug
788                        trace!("Read loop stopping (IceTransport Closed or Failed)");
789                        break;
790                    }
791                }
792            }
793        }
794    }
795
796    async fn run_platform_read_loop(
797        socket: Arc<dyn crate::platform::net::UdpSocket>,
798        inner: Arc<IceTransportInner>,
799    ) {
800        let mut buf = vec![0u8; 1500];
801        let mut marshal_buf = Vec::with_capacity(1500);
802        let sender = IceSocketWrapper::Platform(socket.clone());
803        loop {
804            let result = crate::platform::time::with_timeout(
805                core::time::Duration::from_secs(1),
806                socket.recv_from(&mut buf),
807            )
808            .await;
809            let (len, addr) = match result {
810                Err(_) => continue,
811                Ok(Err(_)) => break,
812                Ok(Ok(v)) => v,
813            };
814            let packet = &buf[..len];
815            if len > 0 {
816                handle_packet(
817                    packet,
818                    addr,
819                    inner.clone(),
820                    sender.clone(),
821                    &mut marshal_buf,
822                )
823                .await;
824            }
825        }
826    }
827
828    /// Read loop for a shared (muxed) UDP socket. Packets arrive via the
829    /// handle's receiver (fed by the shared demux loop); outbound replies go out
830    /// through the same handle.
831    async fn run_shared_udp_read_loop(
832        handle: Arc<shared_udp::SharedUdpHandle>,
833        inner: Arc<IceTransportInner>,
834    ) {
835        let mut state_rx = inner.state.subscribe();
836        let mut marshal_buf = Vec::with_capacity(1500);
837        let sender = IceSocketWrapper::SharedUdp(handle.clone());
838        trace!("Shared UDP read loop started");
839        loop {
840            // state arm polled first each wake (matches the old `biased;`).
841            // The block scopes the pinned futures so `state_rx` is released
842            // before the arm bodies run.
843            let which = {
844                let mut state_fut = core::pin::pin!(state_rx.changed());
845                let mut recv_fut = core::pin::pin!(handle.recv());
846                crate::platform::select::select2(&mut state_fut, &mut recv_fut).await
847            };
848            let packet_opt = match which {
849                crate::platform::select::Either::A(res) => {
850                    if res.is_err()
851                        || matches!(
852                            *state_rx.borrow(),
853                            IceTransportState::Closed | IceTransportState::Failed
854                        )
855                    {
856                        debug!("Shared UDP read loop stopping (IceTransport Closed or Failed)");
857                        break;
858                    }
859                    continue;
860                }
861                crate::platform::select::Either::B(pkt) => pkt,
862            };
863            match packet_opt {
864                Some((packet, addr)) => {
865                    handle_packet(
866                        &packet,
867                        addr,
868                        inner.clone(),
869                        sender.clone(),
870                        &mut marshal_buf,
871                    )
872                    .await;
873                }
874                None => break,
875            }
876        }
877    }
878
879    async fn run_turn_read_loop(
880        client: Arc<TurnClient>,
881        relayed_addr: SocketAddr,
882        inner: Arc<IceTransportInner>,
883    ) {
884        let mut buf = [0u8; 1500];
885        let mut marshal_buf = Vec::with_capacity(1500);
886        let mut state_rx = inner.state.subscribe();
887        trace!("Read loop started for TURN client {}", relayed_addr);
888        loop {
889            let which = {
890                let mut recv_fut = core::pin::pin!(async { client.recv(&mut buf).await });
891                let mut state_fut = core::pin::pin!(state_rx.changed());
892                crate::platform::select::select2(&mut recv_fut, &mut state_fut).await
893            };
894            match which {
895                crate::platform::select::Either::A(result) => match result {
896                    Ok(len) => {
897                        if len > 0 {
898                            IceTransport::handle_turn_packet(
899                                &buf[..len],
900                                &inner,
901                                &client,
902                                relayed_addr,
903                                &mut marshal_buf,
904                            )
905                            .await;
906                        }
907                    }
908                    Err(e) => {
909                        if e.to_string().contains("deadline has elapsed") {
910                            continue;
911                        }
912                        debug!("TURN client recv error: {}", e);
913                        break;
914                    }
915                },
916                crate::platform::select::Either::B(res) => {
917                    if res.is_err()
918                        || matches!(
919                            *state_rx.borrow(),
920                            IceTransportState::Closed | IceTransportState::Failed
921                        )
922                    {
923                        trace!("TURN Read loop stopping (IceTransport Closed or Failed)");
924                        break;
925                    }
926                }
927            }
928        }
929    }
930
931    #[cfg(feature = "std")]
932    async fn run_tcp_listen_loop(listener: Arc<TcpListener>, inner: Arc<IceTransportInner>) {
933        let mut state_rx = inner.state.subscribe();
934        let local_addr = match listener.local_addr() {
935            Ok(a) => a,
936            Err(e) => {
937                debug!("TCP listener local_addr error: {}", e);
938                return;
939            }
940        };
941        trace!("TCP listen loop started for {:?}", local_addr);
942        loop {
943            let which = {
944                let mut accept_fut = core::pin::pin!(listener.accept());
945                let mut state_fut = core::pin::pin!(state_rx.changed());
946                crate::platform::select::select2(&mut accept_fut, &mut state_fut).await
947            };
948            match which {
949                crate::platform::select::Either::A(accept_res) => match accept_res {
950                    Ok((stream, peer_addr)) => {
951                        trace!("TCP accepted connection from {}", peer_addr);
952                        let wrapper = split_tcp_stream(stream, peer_addr);
953                        inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
954                        let _ = inner.gatherer.socket_tx.send(wrapper);
955                    }
956                    Err(e) => {
957                        debug!("TCP accept error: {}", e);
958                        break;
959                    }
960                },
961                crate::platform::select::Either::B(res) => {
962                    if res.is_err()
963                        || matches!(
964                            *state_rx.borrow(),
965                            IceTransportState::Closed | IceTransportState::Failed
966                        )
967                    {
968                        debug!("TCP listen loop stopping (IceTransport Closed or Failed)");
969                        break;
970                    }
971                }
972            }
973        }
974    }
975
976    #[cfg(feature = "std")]
977    async fn run_tcp_read_loop(
978        read: Arc<Mutex<TcpReadHalf>>,
979        write: Arc<Mutex<TcpWriteHalf>>,
980        peer_addr: SocketAddr,
981        inner: Arc<IceTransportInner>,
982    ) {
983        let mut buf = [0u8; 65_535];
984        let mut marshal_buf = Vec::with_capacity(1500);
985        let mut state_rx = inner.state.subscribe();
986        #[cfg(feature = "std")]
987        let sender = IceSocketWrapper::TcpStream(read, write, peer_addr);
988        trace!("TCP read loop started for peer {}", peer_addr);
989        loop {
990            let which = {
991                let mut recv_fut = core::pin::pin!(sender.recv_from(&mut buf));
992                let mut state_fut = core::pin::pin!(state_rx.changed());
993                crate::platform::select::select2(&mut recv_fut, &mut state_fut).await
994            };
995            match which {
996                crate::platform::select::Either::A(result) => match result {
997                    Ok((len, addr)) => {
998                        if len > 0 {
999                            handle_packet(
1000                                &buf[..len],
1001                                addr,
1002                                inner.clone(),
1003                                sender.clone(),
1004                                &mut marshal_buf,
1005                            )
1006                            .await;
1007                        }
1008                    }
1009                    Err(e) => {
1010                        debug!("TCP recv error from {}: {}", peer_addr, e);
1011                        break;
1012                    }
1013                },
1014                crate::platform::select::Either::B(res) => {
1015                    if res.is_err()
1016                        || matches!(
1017                            *state_rx.borrow(),
1018                            IceTransportState::Closed | IceTransportState::Failed
1019                        )
1020                    {
1021                        debug!("TCP read loop stopping (IceTransport Closed or Failed)");
1022                        break;
1023                    }
1024                }
1025            }
1026        }
1027    }
1028
1029    /// Returns an optional cleanup future that should be pushed into `read_futures`
1030    /// by the caller. The future waits up to 5 s for the keepalive response then
1031    /// removes the transaction from `pending_transactions`.
1032    async fn run_keepalive_tick(inner: &Arc<IceTransportInner>) -> Option<BoxFuture<'static, ()>> {
1033        let state = *inner.state.borrow();
1034        if state == IceTransportState::Connected || state == IceTransportState::Disconnected {
1035            if inner.config.transport_mode == crate::TransportMode::WebRtc {
1036                let last_nanos = inner.last_received_nanos.load(Ordering::Relaxed);
1037                let now_nanos = inner.created_at.elapsed().as_nanos() as u64;
1038                let elapsed = Duration::from_nanos(now_nanos.saturating_sub(last_nanos));
1039                let ice_conn_timeout = inner.config.ice_connection_timeout;
1040                let tcp_selected = inner
1041                    .selected_pair
1042                    .lock()
1043                    .as_ref()
1044                    .map(|pair| pair.local.transport == "tcp")
1045                    .unwrap_or(false);
1046                // ICE-TCP recv-only peers (e.g. WHEP) may not send STUN for several seconds
1047                // while DTLS/SRTP comes up; do not flap to Disconnected on the UDP 5s heuristic.
1048                let disconnect_threshold = if tcp_selected {
1049                    ice_conn_timeout.saturating_sub(Duration::from_secs(1))
1050                } else {
1051                    inner.config.ice_disconnect_threshold
1052                };
1053                if elapsed > ice_conn_timeout {
1054                    let _ = inner.set_state(IceTransportState::Failed);
1055                } else if elapsed > disconnect_threshold {
1056                    if state != IceTransportState::Disconnected {
1057                        let _ = inner.set_state(IceTransportState::Disconnected);
1058                    }
1059                } else if state == IceTransportState::Disconnected {
1060                    let _ = inner.set_state(IceTransportState::Connected);
1061                }
1062            }
1063
1064            // Send Keepalive
1065            let pair_opt = inner.selected_pair.lock().clone();
1066            if let Some(pair) = pair_opt {
1067                let socket = inner
1068                    ._socket_rx_keeper
1069                    .borrow()
1070                    .clone()
1071                    .or_else(|| resolve_socket(inner, &pair));
1072                if let Some(socket) = socket {
1073                    let tx_id = random_bytes::<12>();
1074                    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
1075
1076                    let remote_params = inner.remote_parameters.lock().clone();
1077                    if let Some(params) = remote_params {
1078                        let username = format!(
1079                            "{}:{}",
1080                            params.username_fragment,
1081                            inner.local_parameters.lock().username_fragment
1082                        );
1083                        msg.attributes.push(StunAttribute::Username(username));
1084                        msg.attributes
1085                            .push(StunAttribute::Priority(pair.local.priority));
1086
1087                        if let Ok(bytes) = msg.encode(Some(params.password.as_bytes()), true) {
1088                            // Register transaction to avoid "Unmatched transaction" logs
1089                            let (tx, rx) = oneshot::channel();
1090                            {
1091                                let mut map = inner.pending_transactions.lock();
1092                                map.insert(tx_id, tx);
1093                            }
1094
1095                            let inner_weak = Arc::downgrade(inner);
1096                            let cleanup: BoxFuture<'static, ()> = Box::pin(async move {
1097                                let _ = with_timeout(Duration::from_secs(5), rx).await;
1098                                if let Some(inner) = inner_weak.upgrade() {
1099                                    let mut map = inner.pending_transactions.lock();
1100                                    map.remove(&tx_id);
1101                                }
1102                            });
1103
1104                            let _ = socket.send_to(&bytes, pair.remote.address).await;
1105                            return Some(cleanup);
1106                        }
1107                    } else if inner.config.transport_mode != crate::TransportMode::WebRtc
1108                        && let Ok(bytes) = msg.encode(None, false)
1109                    {
1110                        let _ = socket.send_to(&bytes, pair.remote.address).await;
1111                    }
1112                }
1113            }
1114        }
1115        None
1116    }
1117
1118    /// Periodically refresh TURN allocations, permissions, and channel bindings
1119    /// to prevent them from expiring. Per RFC 5766:
1120    ///   - Allocation lifetime: 600s (default), refresh before expiry
1121    ///   - Permission lifetime: 300s, must be refreshed
1122    ///   - ChannelBind lifetime: 600s, must be refreshed
1123    ///
1124    /// This runs every ~25s — well under all three timeouts AND under typical
1125    /// UDP NAT mapping idle timeouts, so the client<->TURN-server 5-tuple stays
1126    /// mapped even for relays that carry no media (the bidirectional Refresh
1127    /// traffic refreshes the NAT). See the interval setup in `run`.
1128    ///
1129    /// Each request is awaited directly (no spawning) so that a 401/438
1130    /// stale-nonce response is detected immediately and the nonce is refreshed
1131    /// before retrying — preventing silent refresh failures that eventually let
1132    /// ChannelBindings expire and cause SCTP disconnects.
1133    async fn run_turn_refresh(inner: &Arc<IceTransportInner>) {
1134        // Acquire the in-progress guard. If a previous refresh is somehow still
1135        // running (e.g. the runner raced two ticks), bail out instead of running
1136        // two concurrent refreshes that could interleave nonce updates.
1137        if inner
1138            .turn_refresh_in_progress
1139            .swap(true, core::sync::atomic::Ordering::SeqCst)
1140        {
1141            return;
1142        }
1143        // RAII: guarantees the flag is cleared on every exit path, including
1144        // early returns and panics, so the timer never deadlocks on refresh.
1145        struct RefreshGuard<'a>(&'a core::sync::atomic::AtomicBool);
1146        impl Drop for RefreshGuard<'_> {
1147            fn drop(&mut self) {
1148                self.0.store(false, core::sync::atomic::Ordering::SeqCst);
1149            }
1150        }
1151        let _guard = RefreshGuard(&inner.turn_refresh_in_progress);
1152
1153        let state = *inner.state.borrow();
1154        if state != IceTransportState::Connected && state != IceTransportState::Disconnected {
1155            return;
1156        }
1157
1158        let all_clients: Vec<(SocketAddr, Arc<TurnClient>)> = {
1159            let clients = inner.gatherer.turn_clients.lock();
1160            clients.iter().map(|(k, v)| (*k, v.clone())).collect()
1161        };
1162
1163        if all_clients.is_empty() {
1164            return;
1165        }
1166
1167        let pair_opt = inner.selected_pair.lock().clone();
1168
1169        let remote_addr_for_perm = pair_opt.as_ref().map(|p| p.remote.address);
1170
1171        for (relay_local_addr, client) in all_clients {
1172            Self::refresh_one_turn_client(inner, relay_local_addr, &client, remote_addr_for_perm)
1173                .await;
1174        }
1175    }
1176
1177    async fn refresh_one_turn_client(
1178        inner: &Arc<IceTransportInner>,
1179        _relay_local_addr: SocketAddr,
1180        client: &Arc<TurnClient>,
1181        remote_addr_opt: Option<SocketAddr>,
1182    ) {
1183        async fn send_and_await_inner(
1184            client: &Arc<TurnClient>,
1185            inner: &Arc<IceTransportInner>,
1186            bytes: Vec<u8>,
1187            tx_id: [u8; 12],
1188        ) -> Option<StunDecoded> {
1189            let (tx, rx) = oneshot::channel();
1190            inner.pending_transactions.lock().insert(tx_id, tx);
1191            if let Err(e) = client.send(&bytes).await {
1192                debug!("TURN refresh send failed: {}", e);
1193                inner.pending_transactions.lock().remove(&tx_id);
1194                return None;
1195            }
1196            match with_timeout(Duration::from_secs(5), rx).await {
1197                Ok(Ok(msg)) => Some(msg),
1198                _ => {
1199                    inner.pending_transactions.lock().remove(&tx_id);
1200                    None
1201                }
1202            }
1203        }
1204
1205        // 1. Refresh the allocation (extends lifetime).
1206        //    On 401/438 update the nonce and retry once.
1207        'alloc: for attempt in 0..2u8 {
1208            match client.create_refresh_packet().await {
1209                Ok((bytes, tx_id)) => {
1210                    match send_and_await_inner(client, inner, bytes, tx_id).await {
1211                        Some(msg) if msg.class == StunClass::SuccessResponse => {
1212                            trace!("TURN allocation refreshed successfully");
1213                            break 'alloc;
1214                        }
1215                        Some(msg)
1216                            if matches!(msg.error_code, Some(401) | Some(438)) && attempt == 0 =>
1217                        {
1218                            // Stale nonce: update and retry
1219                            if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
1220                                debug!(
1221                                    "TURN Refresh got {}: updating nonce, retrying",
1222                                    msg.error_code.unwrap_or(0)
1223                                );
1224                                client.update_nonce(realm, nonce).await;
1225                            }
1226                            continue 'alloc;
1227                        }
1228                        Some(msg) => {
1229                            debug!("TURN Refresh failed: error={:?}", msg.error_code);
1230                        }
1231                        None => {
1232                            debug!("TURN Refresh timeout or send error");
1233                        }
1234                    }
1235                }
1236                Err(e) => debug!("TURN Refresh packet creation failed: {}", e),
1237            }
1238            break;
1239        }
1240
1241        // 2. Refresh permission for the remote peer (if known).
1242        //    This keeps the relay→peer path warm even when the selected pair uses a
1243        //    host/srflx candidate, so that the relay path is available as a fallback.
1244        if let Some(remote_addr) = remote_addr_opt {
1245            'perm: for attempt in 0..2u8 {
1246                match client.create_permission_packet(remote_addr).await {
1247                    Ok((bytes, tx_id)) => {
1248                        match send_and_await_inner(client, inner, bytes, tx_id).await {
1249                            Some(msg) if msg.class == StunClass::SuccessResponse => {
1250                                trace!("TURN permission refreshed for {}", remote_addr);
1251                                break 'perm;
1252                            }
1253                            Some(msg)
1254                                if matches!(msg.error_code, Some(401) | Some(438))
1255                                    && attempt == 0 =>
1256                            {
1257                                if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
1258                                    debug!(
1259                                        "TURN CreatePermission got {}: updating nonce, retrying",
1260                                        msg.error_code.unwrap_or(0)
1261                                    );
1262                                    client.update_nonce(realm, nonce).await;
1263                                }
1264                                continue 'perm;
1265                            }
1266                            Some(msg) => {
1267                                debug!(
1268                                    "TURN CreatePermission refresh failed: error={:?}",
1269                                    msg.error_code
1270                                );
1271                            }
1272                            None => {
1273                                debug!("TURN CreatePermission refresh timeout or send error");
1274                            }
1275                        }
1276                    }
1277                    Err(e) => debug!("TURN CreatePermission packet creation failed: {}", e),
1278                }
1279                break;
1280            }
1281        }
1282
1283        // 3. Refresh channel bindings for all bound peers.
1284        //    On 401/438 update the nonce and retry once per channel.
1285        let bound_peers = client.bound_peers().await;
1286        let num_bindings = bound_peers.len();
1287        for peer in bound_peers {
1288            if let Some(channel) = client.get_channel(peer).await {
1289                'chan: for attempt in 0..2u8 {
1290                    match client.create_channel_rebind_packet(peer, channel).await {
1291                        Ok((bytes, tx_id)) => {
1292                            match send_and_await_inner(client, inner, bytes, tx_id).await {
1293                                Some(msg) if msg.class == StunClass::SuccessResponse => {
1294                                    trace!(
1295                                        "TURN ChannelBind refreshed: {} -> ch {}",
1296                                        peer, channel
1297                                    );
1298                                    break 'chan;
1299                                }
1300                                Some(msg)
1301                                    if matches!(msg.error_code, Some(401) | Some(438))
1302                                        && attempt == 0 =>
1303                                {
1304                                    if let (Some(realm), Some(nonce)) = (msg.realm, msg.nonce) {
1305                                        debug!(
1306                                            "TURN ChannelBind got {}: updating nonce, retrying ch {}",
1307                                            msg.error_code.unwrap_or(0),
1308                                            channel
1309                                        );
1310                                        client.update_nonce(realm, nonce).await;
1311                                    }
1312                                    continue 'chan;
1313                                }
1314                                Some(msg) => {
1315                                    debug!(
1316                                        "TURN ChannelBind refresh failed: ch={} error={:?}",
1317                                        channel, msg.error_code
1318                                    );
1319                                }
1320                                None => {
1321                                    debug!(
1322                                        "TURN ChannelBind refresh timeout or send error: ch={}",
1323                                        channel
1324                                    );
1325                                }
1326                            }
1327                        }
1328                        Err(e) => {
1329                            debug!("TURN ChannelBind refresh packet creation failed: {}", e);
1330                        }
1331                    }
1332                    break;
1333                }
1334            }
1335        }
1336
1337        debug!(
1338            "TURN refresh done: allocation + {} permission + {} channel bindings",
1339            if remote_addr_opt.is_some() { 1 } else { 0 },
1340            num_bindings
1341        );
1342    }
1343}
1344
1345impl IceTransport {
1346    pub fn new(config: RtcConfiguration) -> (Self, impl core::future::Future<Output = ()> + Send) {
1347        let (candidate_tx, _) = broadcast::channel(100);
1348        let (socket_tx, socket_rx) = crate::platform::sync::mpsc::unbounded_channel();
1349        let gatherer = IceGatherer::new(config.clone(), candidate_tx.clone(), socket_tx);
1350        let (state_tx, state_rx) = watch::channel(IceTransportState::New);
1351        let runner_state_rx = state_tx.subscribe();
1352        let (gathering_state_tx, gathering_state_rx) = watch::channel(IceGathererState::New);
1353        let (selected_socket_tx, selected_socket_rx) = watch::channel(None);
1354        let (selected_rtcp_socket_tx, selected_rtcp_socket_rx) = watch::channel(None);
1355        let (selected_pair_tx, selected_pair_rx) = watch::channel(None);
1356        let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
1357        let (nomination_complete_tx, nomination_complete_rx) = watch::channel(None);
1358
1359        let inner = IceTransportInner {
1360            state: state_tx,
1361            _state_rx_keeper: state_rx,
1362            gathering_state: gathering_state_tx,
1363            _gathering_state_rx_keeper: gathering_state_rx,
1364            role: crate::platform::sync::Mutex::new(IceRole::Controlled),
1365            selected_pair: crate::platform::sync::Mutex::new(None),
1366            local_candidates: Mutex::new(Vec::new()),
1367            remote_candidates: crate::platform::sync::Mutex::new(Vec::new()),
1368            gather_state: crate::platform::sync::Mutex::new(IceGathererState::New),
1369            config: config.clone(),
1370            gatherer,
1371            local_parameters: crate::platform::sync::Mutex::new(IceParameters::generate()),
1372            remote_parameters: crate::platform::sync::Mutex::new(None),
1373            pending_transactions: crate::platform::sync::Mutex::new(BTreeMap::new()),
1374            data_receiver: crate::platform::sync::Mutex::new(None),
1375            buffered_packets: crate::platform::sync::Mutex::new(VecDeque::new()),
1376            selected_socket: selected_socket_tx,
1377            _socket_rx_keeper: selected_socket_rx,
1378            selected_rtcp_socket: selected_rtcp_socket_tx,
1379            _rtcp_socket_rx_keeper: selected_rtcp_socket_rx,
1380            selected_pair_notifier: selected_pair_tx,
1381            _selected_pair_rx_keeper: selected_pair_rx,
1382            created_at: Instant::now(),
1383            last_received_nanos: AtomicU64::new(0),
1384            candidate_tx: candidate_tx.clone(),
1385            cmd_tx,
1386            checking_pairs: Mutex::new(alloc::collections::BTreeSet::new()),
1387            nomination_complete: nomination_complete_tx,
1388            _nomination_complete_rx: nomination_complete_rx,
1389            restart_requested: core::sync::atomic::AtomicBool::new(false),
1390            nomination_generation: crate::platform::atomic64::AtomicU64::new(0),
1391            mdns_hostname: {
1392                #[cfg(feature = "std")]
1393                {
1394                    config
1395                        .enable_mdns
1396                        .then(crate::transports::ice::mdns::MdnsResponder::generate_hostname)
1397                }
1398                #[cfg(not(feature = "std"))]
1399                {
1400                    None
1401                }
1402            },
1403            turn_refresh_in_progress: core::sync::atomic::AtomicBool::new(false),
1404            upnp_refresh_in_progress: core::sync::atomic::AtomicBool::new(false),
1405            buffer_stats: Arc::new(BufferStats::default()),
1406        };
1407        let inner = Arc::new(inner);
1408        inner.gatherer.set_transport(Arc::downgrade(&inner));
1409
1410        let runner = IceTransportRunner {
1411            inner: inner.clone(),
1412            socket_rx,
1413            candidate_rx: candidate_tx.subscribe(),
1414            cmd_rx,
1415            state_rx: runner_state_rx,
1416        };
1417
1418        (Self { inner }, runner.run())
1419    }
1420
1421    pub fn state(&self) -> IceTransportState {
1422        *self.inner.state.borrow()
1423    }
1424
1425    pub fn subscribe_state(&self) -> watch::Receiver<IceTransportState> {
1426        self.inner.state.subscribe()
1427    }
1428
1429    pub fn subscribe_gathering_state(&self) -> watch::Receiver<IceGathererState> {
1430        self.inner.gathering_state.subscribe()
1431    }
1432
1433    pub fn subscribe_candidates(&self) -> broadcast::Receiver<IceCandidate> {
1434        self.inner.candidate_tx.subscribe()
1435    }
1436
1437    pub fn subscribe_selected_socket(&self) -> watch::Receiver<Option<IceSocketWrapper>> {
1438        self.inner.selected_socket.subscribe()
1439    }
1440
1441    pub(crate) fn subscribe_selected_rtcp_socket(
1442        &self,
1443    ) -> watch::Receiver<Option<IceSocketWrapper>> {
1444        self.inner.selected_rtcp_socket.subscribe()
1445    }
1446
1447    pub fn subscribe_selected_pair(&self) -> watch::Receiver<Option<IceCandidatePair>> {
1448        self.inner.selected_pair_notifier.subscribe()
1449    }
1450
1451    /// Subscribe to the nomination-complete signal.
1452    /// Yields `Some(true)` when nomination succeeds, `Some(false)` when it fails.
1453    /// The controlled side yields `Some(true)` immediately (it has no nomination to perform).
1454    pub fn subscribe_nomination_complete(&self) -> watch::Receiver<Option<bool>> {
1455        self.inner.nomination_complete.subscribe()
1456    }
1457
1458    /// When the controlling peer has no local TCP candidates it may connect inbound
1459    /// without sending USE-CANDIDATE. Complete nomination once a passive TCP stream exists.
1460    pub fn nudge_passive_tcp_nomination(&self) {
1461        if *self.inner.role.lock() != IceRole::Controlled {
1462            return;
1463        }
1464        if self.inner.nomination_complete.borrow().is_some() {
1465            return;
1466        }
1467        let inner = self.inner.clone();
1468        debug!("ICE: nudging passive TCP nomination (controlled, awaiting inbound TCP)");
1469        let rt_handle = inner.config.runtime_handle.clone();
1470        crate::spawn_rtc(rt_handle.as_ref(), tracing::Span::current(), async move {
1471            let streams: Vec<_> = inner
1472                .gatherer
1473                .tcp_streams
1474                .lock()
1475                .values()
1476                .cloned()
1477                .collect();
1478            for wrapper in streams {
1479                #[cfg(feature = "std")]
1480                if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper {
1481                    #[cfg(feature = "std")]
1482                    complete_controlled_inbound_tcp_nomination(&wrapper, peer, inner).await;
1483                    #[cfg(not(feature = "std"))]
1484                    {
1485                        let _ = (&wrapper, &peer, &inner);
1486                    }
1487                    return;
1488                }
1489            }
1490        });
1491    }
1492
1493    pub fn gather_state(&self) -> IceGathererState {
1494        self.inner.gatherer.state()
1495    }
1496
1497    pub fn role(&self) -> IceRole {
1498        *self.inner.role.lock()
1499    }
1500
1501    pub fn local_candidates(&self) -> Vec<IceCandidate> {
1502        self.inner.gatherer.local_candidates()
1503    }
1504
1505    pub(crate) fn local_rtcp_addr(&self) -> Option<SocketAddr> {
1506        self.inner
1507            .gatherer
1508            .local_candidates()
1509            .into_iter()
1510            .find(|candidate| candidate.component == 2)
1511            .map(|candidate| candidate.address)
1512    }
1513
1514    pub fn remote_candidates(&self) -> Vec<IceCandidate> {
1515        self.inner.remote_candidates.lock().clone()
1516    }
1517
1518    pub fn local_parameters(&self) -> IceParameters {
1519        self.inner.local_parameters.lock().clone()
1520    }
1521
1522    pub fn set_remote_parameters(&self, params: IceParameters) {
1523        *self.inner.remote_parameters.lock() = Some(params);
1524    }
1525
1526    /// Restart ICE (RFC 8445 §9): roll fresh local credentials and reset all
1527    /// connectivity-check / nomination state so checks re-run end-to-end.
1528    ///
1529    /// This is a credential-only restart: gathered candidates and their
1530    /// sockets stay in place, because the dominant trigger is a re-INVITE /
1531    /// `restartIce()` where the local network interfaces did not change. The
1532    /// next local description automatically carries the new ufrag/pwd (both
1533    /// `build_description` paths read `local_parameters` live). DTLS and SRTP
1534    /// state are untouched — media resumes over the (possibly different)
1535    /// selected pair once checks and nomination complete again.
1536    ///
1537    /// A remote-initiated restart (peer offers new ice-ufrag/ice-pwd) is
1538    /// detected in [`Self::start`], which runs the same reset via
1539    /// `restart_internal` *without* marking it as locally initiated.
1540    pub async fn restart(&self) -> RtcResult<()> {
1541        // Mark this as locally initiated *before* rolling credentials, so the
1542        // peer's answer (which carries our new ufrag/pwd) is recognised as the
1543        // completion of our own restart rather than a fresh remote one. This
1544        // flag must NOT be set by a remote-triggered restart: leaving it set
1545        // after `restart_internal` would make the *next* remote restart look
1546        // like the completion of a local restart and skip it (issue #54).
1547        self.inner
1548            .restart_requested
1549            .store(true, core::sync::atomic::Ordering::SeqCst);
1550        self.restart_internal().await
1551    }
1552
1553    /// Shared restart body used by both locally- and remotely-initiated
1554    /// restarts. Deliberately does not touch `restart_requested`; see
1555    /// [`Self::restart`] for why only the local path may set that flag.
1556    async fn restart_internal(&self) -> RtcResult<()> {
1557        // 1. Fresh credentials. A new tie_breaker also makes us win/lose role
1558        //    conflicts per RFC 8445 §5.1.1.1 semantics for the new session.
1559        *self.inner.local_parameters.lock() = IceParameters::generate();
1560
1561        // 2. Reset check/nomination state. The selected socket stays published
1562        //    so DTLS/SRTP keep running on the old path until a new pair is
1563        //    nominated — media pauses, but the session does not tear down.
1564        self.inner.checking_pairs.lock().await.clear();
1565        self.inner.pending_transactions.lock().clear();
1566        *self.inner.selected_pair.lock() = None;
1567        let _ = self.inner.selected_pair_notifier.send(None);
1568        // Invalidate any in-flight path-verification task from before the
1569        // restart so it cannot re-select the old pair afterwards.
1570        self.inner
1571            .nomination_generation
1572            .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
1573        let _ = self.inner.nomination_complete.send(None);
1574        let _ = self.inner.set_state(IceTransportState::Checking);
1575
1576        // 3. Shared UDP mux sessions are keyed by server ufrag, so the mux
1577        //    socket must be re-registered under the new ufrag or every inbound
1578        //    STUN binding request would be demuxed to a dead session.
1579        self.rebind_shared_udp_mux().await?;
1580
1581        debug!(
1582            label = self.inner.config.label.as_deref().unwrap_or("-"),
1583            "ICE restart: new ufrag={}, pwd=<redacted>",
1584            self.inner.local_parameters.lock().username_fragment
1585        );
1586        Ok(())
1587    }
1588
1589    /// Re-register this transport on the shared UDP mux socket under the
1590    /// current (fresh) ufrag. No-op when mux is not in use.
1591    async fn rebind_shared_udp_mux(&self) -> RtcResult<()> {
1592        let gatherer = &self.inner.gatherer;
1593        let listen_key = {
1594            let regs = gatherer.shared_udp_regs.lock();
1595            let Some(reg) = regs.first() else {
1596                return Ok(()); // mux not in use
1597            };
1598            reg.listen_key()
1599        };
1600
1601        // Dropping the old registrations removes the old ufrag session; the
1602        // old read loop exits once its channel's senders are gone.
1603        gatherer.shared_udp_regs.lock().clear();
1604
1605        let ufrag = self.inner.local_parameters.lock().username_fragment.clone();
1606        let (_local_addr, handle, registration) =
1607            shared_udp::acquire(listen_key, ufrag).await.map_err(|e| {
1608                // Fall back to dropping the session entirely rather than
1609                // leaving the mux registered under the stale ufrag.
1610                RtcError::Internal(format!("shared UDP mux rebind failed: {e:#}"))
1611            })?;
1612        gatherer.shared_udp_regs.lock().push(registration);
1613
1614        let wrapper = IceSocketWrapper::SharedUdp(Arc::new(handle));
1615        *gatherer.shared_udp_socket.lock() = Some(wrapper.clone());
1616        // Publish the new handle so the runner starts a read loop on it.
1617        gatherer.socket_tx.send(wrapper).ok();
1618        Ok(())
1619    }
1620
1621    fn start_keepalive(&self) {
1622        // Handled by runner
1623    }
1624
1625    pub fn start_gathering(&self) -> RtcResult<()> {
1626        {
1627            let mut state = self.inner.gather_state.lock();
1628            if *state == IceGathererState::Complete || *state == IceGathererState::Gathering {
1629                return Ok(());
1630            }
1631            *state = IceGathererState::Gathering;
1632            let _ = self.inner.gathering_state.send(IceGathererState::Gathering);
1633        }
1634
1635        let _ = self.inner.cmd_tx.send(IceCommand::StartGathering);
1636        Ok(())
1637    }
1638
1639    pub async fn start(&self, remote: IceParameters) -> RtcResult<()> {
1640        // Remote-initiated ICE restart detection (RFC 8445 §9): the peer
1641        // signals a restart by changing its ice-ufrag/ice-pwd after the
1642        // session was established. When we did NOT ask for a restart
1643        // ourselves, mirror it: roll fresh local credentials so our answer
1644        // carries new credentials too, and reset check state. (When we DID
1645        // request a restart, `restart()` already reset everything — the
1646        // changed peer credentials are just the remote half of our restart.)
1647        let previous_remote = self.inner.remote_parameters.lock().clone();
1648        if let Some(prev) = previous_remote
1649            && (prev.username_fragment != remote.username_fragment
1650                || prev.password != remote.password)
1651        {
1652            if self
1653                .inner
1654                .restart_requested
1655                .swap(false, core::sync::atomic::Ordering::SeqCst)
1656            {
1657                debug!(
1658                    label = self.inner.config.label.as_deref().unwrap_or("-"),
1659                    "ICE restart completing: peer re-signalled credentials"
1660                );
1661            } else {
1662                debug!(
1663                    label = self.inner.config.label.as_deref().unwrap_or("-"),
1664                    "remote ICE restart detected (ufrag/pwd changed): restarting locally"
1665                );
1666                self.restart_internal().await?;
1667            }
1668        } else {
1669            self.inner
1670                .restart_requested
1671                .store(false, core::sync::atomic::Ordering::SeqCst);
1672        }
1673
1674        self.start_gathering()?;
1675        self.start_keepalive();
1676        {
1677            let mut params = self.inner.remote_parameters.lock();
1678            *params = Some(remote);
1679        }
1680        self.inner.set_state(IceTransportState::Checking);
1681        self.try_connectivity_checks();
1682        Ok(())
1683    }
1684
1685    pub async fn start_direct(&self, remote_addr: SocketAddr) -> RtcResult<()> {
1686        self.start_gathering()?;
1687        self.start_keepalive();
1688
1689        // Wait for a suitable local candidate
1690        // If remote is not loopback, we prefer a non-loopback local candidate to avoid os error 49 (EADDRNOTAVAIL)
1691        let mut rx = self.subscribe_candidates();
1692        let start = Instant::now();
1693        let timeout_dur = Duration::from_secs(2);
1694
1695        let is_suitable = |c: &IceCandidate| -> bool {
1696            if !remote_addr.ip().is_loopback() && c.address.ip().is_loopback() {
1697                return false;
1698            }
1699            true
1700        };
1701
1702        let mut best_local: Option<IceCandidate> = None;
1703
1704        // 1. Check existing candidates
1705        {
1706            let candidates = self.inner.gatherer.local_candidates();
1707            for c in candidates {
1708                if is_suitable(&c) {
1709                    best_local = Some(c);
1710                    break;
1711                }
1712            }
1713        }
1714
1715        // 2. If not found, wait for more
1716        if best_local.is_none() {
1717            loop {
1718                let remaining = timeout_dur
1719                    .checked_sub(start.elapsed())
1720                    .unwrap_or(Duration::ZERO);
1721                if remaining.is_zero() {
1722                    break;
1723                }
1724
1725                match with_timeout(remaining, rx.recv()).await {
1726                    Ok(Ok(c)) => {
1727                        if is_suitable(&c) {
1728                            best_local = Some(c);
1729                            break;
1730                        }
1731                    }
1732                    _ => break,
1733                }
1734            }
1735        }
1736
1737        // 3. Fallback to any candidate
1738        let local = if let Some(best) = best_local {
1739            best
1740        } else if let Some(first) = self.inner.gatherer.local_candidates().first() {
1741            first.clone()
1742        } else {
1743            return Err(RtcError::Internal(format!(
1744                "No local candidates gathered for direct connection"
1745            )));
1746        };
1747
1748        let remote = IceCandidate::host(remote_addr, 1);
1749        let pair = IceCandidatePair::new(local, remote);
1750
1751        *self.inner.selected_pair.lock() = Some(pair.clone());
1752        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
1753        if let Some(socket) = resolve_socket(&self.inner, &pair) {
1754            let _ = self.inner.selected_socket.send(Some(socket.clone()));
1755            publish_selected_rtcp_socket(&self.inner, Some(socket));
1756        }
1757        let _ = self.inner.set_state(IceTransportState::Connected);
1758        Ok(())
1759    }
1760
1761    /// Set up a direct UDP socket for RTP mode without any ICE gathering,
1762    /// STUN lookups, or connectivity checks.
1763    /// Binds a single socket, registers it, and marks the transport as connected.
1764    pub async fn setup_direct_rtp(&self, remote_addr: SocketAddr) -> RtcResult<SocketAddr> {
1765        self.setup_direct_rtp_with_rtcp(remote_addr, false).await
1766    }
1767
1768    pub(crate) async fn setup_direct_rtp_with_rtcp(
1769        &self,
1770        remote_addr: SocketAddr,
1771        bind_rtcp: bool,
1772    ) -> RtcResult<SocketAddr> {
1773        let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
1774            bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
1775                get_local_ip().unwrap_or(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED))
1776            })
1777        } else if let Ok(ip) = get_local_ip() {
1778            ip
1779        } else {
1780            IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
1781        };
1782
1783        let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
1784        let local_addr = socket.local_addr()?;
1785
1786        // Register the socket wrapper for the read loop (handled by runner);
1787        // backend bookkeeping already happened in bind_one.
1788        self.inner.gatherer.register_bound(&socket);
1789
1790        // Build a local candidate for SDP generation
1791        let mut cand_addr = local_addr;
1792        let mut upnp_external_addr: Option<SocketAddr> = None;
1793
1794        // Try UPnP if enabled (for RTP mode behind NAT) — std-only
1795        // (UPnP is excluded from the embedded target)
1796        #[cfg(feature = "std")]
1797        if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
1798        {
1799            let mut mapper = UpnpPortMapper::with_lease_duration(
1800                local_addr,
1801                self.inner.config.upnp_lease_duration,
1802            );
1803            if let Err(e) = mapper.discover().await {
1804                trace!("UPnP discovery failed for RTP mode: {}", e);
1805            } else if let Ok(ext_addr) = mapper.add_mapping(0).await {
1806                debug!(
1807                    "UPnP mapping created for RTP mode: {} -> {}",
1808                    local_addr, ext_addr
1809                );
1810                cand_addr.set_ip(ext_addr.ip());
1811                cand_addr.set_port(ext_addr.port());
1812                upnp_external_addr = Some(ext_addr);
1813                self.inner.gatherer.upnp_mappers.lock().push(mapper);
1814            } else {
1815                debug!("UPnP mapping failed for RTP mode, using local address");
1816            }
1817        }
1818
1819        // Fall back to external_ip config if UPnP not available
1820        if upnp_external_addr.is_none() {
1821            if let Some(ext_ip) = &self.inner.config.external_ip {
1822                if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
1823                    && !bind_ip.is_loopback()
1824                {
1825                    cand_addr.set_ip(parsed_ip);
1826                }
1827            } else if bind_ip.is_unspecified()
1828                && let Ok(local_ip) = get_local_ip()
1829            {
1830                cand_addr.set_ip(local_ip);
1831            }
1832        }
1833
1834        // Apply external_port override (for NAT port forwarding)
1835        if upnp_external_addr.is_none()
1836            && let Some(ext_port) = self.inner.config.external_port
1837            && !bind_ip.is_loopback()
1838        {
1839            cand_addr.set_port(ext_port);
1840        }
1841
1842        let mut local_candidate = IceCandidate::host(cand_addr, 1);
1843        if cand_addr != local_addr {
1844            local_candidate.related_address = Some(local_addr);
1845        }
1846        let mut rtcp_socket = None;
1847        let mut rtcp_candidate = None;
1848        if bind_rtcp {
1849            let (rtcp, candidate) =
1850                bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
1851            rtcp_socket = Some(rtcp);
1852            rtcp_candidate = Some(candidate);
1853        }
1854        self.inner.gatherer.push_candidate(local_candidate.clone());
1855        if let Some(candidate) = rtcp_candidate {
1856            self.inner.gatherer.push_candidate(candidate);
1857        }
1858
1859        // Set gathering as complete
1860        *self.inner.gatherer.state.lock() = IceGathererState::Complete;
1861        let _ = self.inner.gathering_state.send(IceGathererState::Complete);
1862
1863        // Set up the selected pair
1864        let remote_candidate = IceCandidate::host(remote_addr, 1);
1865        let pair = IceCandidatePair::new(local_candidate, remote_candidate);
1866        *self.inner.selected_pair.lock() = Some(pair.clone());
1867        let _ = self.inner.selected_pair_notifier.send(Some(pair));
1868        let _ = self
1869            .inner
1870            .selected_socket
1871            .send(Some(socket.clone()));
1872        let rtcp_socket = rtcp_socket.unwrap_or_else(|| socket.clone());
1873        let _ = self
1874            .inner
1875            .selected_rtcp_socket
1876            .send(Some(rtcp_socket));
1877        let _ = self.inner.set_state(IceTransportState::Connected);
1878
1879        Ok(cand_addr)
1880    }
1881
1882    /// Set up a direct UDP socket for RTP mode (offer side, no remote addr yet).
1883    /// Binds a socket and registers the local candidate, but does NOT set the
1884    /// selected pair or transition to Connected.
1885    pub async fn setup_direct_rtp_offer(&self) -> RtcResult<SocketAddr> {
1886        self.setup_direct_rtp_offer_with_rtcp(false).await
1887    }
1888
1889    pub(crate) async fn setup_direct_rtp_offer_with_rtcp(
1890        &self,
1891        bind_rtcp: bool,
1892    ) -> RtcResult<SocketAddr> {
1893        let bind_ip = if let Some(bind_ip_str) = &self.inner.config.bind_ip {
1894            bind_ip_str.parse::<IpAddr>().unwrap_or_else(|_| {
1895                get_local_ip().unwrap_or(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED))
1896            })
1897        } else if let Ok(ip) = get_local_ip() {
1898            ip
1899        } else {
1900            IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
1901        };
1902
1903        let socket = self.inner.gatherer.bind_socket(bind_ip).await?;
1904        let local_addr = socket.local_addr()?;
1905        self.inner.gatherer.register_bound(&socket);
1906
1907        let mut cand_addr = local_addr;
1908        let mut upnp_external_addr: Option<SocketAddr> = None;
1909
1910        // Try UPnP if enabled (for RTP mode behind NAT) — std-only
1911        // (UPnP is excluded from the embedded target)
1912        #[cfg(feature = "std")]
1913        if self.inner.config.enable_upnp && !local_addr.ip().is_loopback() && !local_addr.is_ipv6()
1914        {
1915            let mut mapper = UpnpPortMapper::with_lease_duration(
1916                local_addr,
1917                self.inner.config.upnp_lease_duration,
1918            );
1919            if let Err(e) = mapper.discover().await {
1920                trace!("UPnP discovery failed for RTP offer mode: {}", e);
1921            } else if let Ok(ext_addr) = mapper.add_mapping(0).await {
1922                debug!(
1923                    "UPnP mapping created for RTP offer mode: {} -> {}",
1924                    local_addr, ext_addr
1925                );
1926                cand_addr.set_ip(ext_addr.ip());
1927                cand_addr.set_port(ext_addr.port());
1928                upnp_external_addr = Some(ext_addr);
1929                self.inner.gatherer.upnp_mappers.lock().push(mapper);
1930            } else {
1931                debug!("UPnP mapping failed for RTP offer mode, using local address");
1932            }
1933        }
1934
1935        // Fall back to external_ip config if UPnP not available
1936        if upnp_external_addr.is_none() {
1937            if let Some(ext_ip) = &self.inner.config.external_ip {
1938                if let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
1939                    && !bind_ip.is_loopback()
1940                {
1941                    cand_addr.set_ip(parsed_ip);
1942                }
1943            } else if bind_ip.is_unspecified()
1944                && let Ok(local_ip) = get_local_ip()
1945            {
1946                cand_addr.set_ip(local_ip);
1947            }
1948        }
1949
1950        // Apply external_port override (for NAT port forwarding)
1951        if upnp_external_addr.is_none()
1952            && let Some(ext_port) = self.inner.config.external_port
1953            && !bind_ip.is_loopback()
1954        {
1955            cand_addr.set_port(ext_port);
1956        }
1957
1958        let mut local_candidate = IceCandidate::host(cand_addr, 1);
1959        if cand_addr != local_addr {
1960            local_candidate.related_address = Some(local_addr);
1961        }
1962        let mut rtcp_socket = None;
1963        let mut rtcp_candidate = None;
1964        if bind_rtcp {
1965            let (rtcp, candidate) =
1966                bind_direct_rtcp_socket(&self.inner, local_addr, cand_addr.ip()).await?;
1967            rtcp_socket = Some(rtcp);
1968            rtcp_candidate = Some(candidate);
1969        }
1970        self.inner.gatherer.push_candidate(local_candidate);
1971        if let Some(candidate) = rtcp_candidate {
1972            self.inner.gatherer.push_candidate(candidate);
1973        }
1974        if let Some(rtcp_socket) = rtcp_socket {
1975            let _ = self
1976                .inner
1977                .selected_rtcp_socket
1978                .send(Some(rtcp_socket));
1979        }
1980
1981        *self.inner.gatherer.state.lock() = IceGathererState::Complete;
1982        let _ = self.inner.gathering_state.send(IceGathererState::Complete);
1983
1984        Ok(cand_addr)
1985    }
1986
1987    /// Complete the RTP direct connection by setting the remote address.
1988    /// Call after setup_direct_rtp_offer when the answer arrives with the remote address.
1989    pub fn complete_direct_rtp(&self, remote_addr: SocketAddr) {
1990        let remote_candidate = IceCandidate::host(remote_addr, 1);
1991        let local_candidate = self
1992            .inner
1993            .gatherer
1994            .local_candidates()
1995            .into_iter()
1996            .find(|candidate| candidate.component == 1)
1997            .unwrap_or_else(|| {
1998                IceCandidate::host(
1999                    SocketAddr::new(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST), 0),
2000                    1,
2001                )
2002            });
2003        let pair = IceCandidatePair::new(local_candidate, remote_candidate);
2004        *self.inner.selected_pair.lock() = Some(pair.clone());
2005        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
2006        if let Some(socket) = resolve_socket(&self.inner, &pair) {
2007            let _ = self.inner.selected_socket.send(Some(socket.clone()));
2008            publish_selected_rtcp_socket(&self.inner, Some(socket));
2009        }
2010        let _ = self.inner.set_state(IceTransportState::Connected);
2011    }
2012
2013    /// Best-effort explicit destruction of all TURN allocations (RFC 5766 §7.4).
2014    ///
2015    /// Sends a Refresh request with LIFETIME=0 to each TURN server. The server
2016    /// releases the allocation on receipt of the request; the success response
2017    /// is informational. Runs on a detached task so it never blocks `stop()` /
2018    /// `PeerConnection::close()`.
2019    fn destroy_turn_allocations_best_effort(&self) {
2020        // Synchronous, no spawn/await: build a Refresh(LIFETIME=0) packet and
2021        // fire it off via non-blocking UDP. We do NOT wait for a response or
2022        // retry on stale-nonce — the server destroys the allocation on receipt
2023        // (RFC 5766 §7.4) and eventually times it out if the packet is lost.
2024        let clients: Vec<Arc<TurnClient>> = {
2025            let map = self.inner.gatherer.turn_clients.lock();
2026            map.values().cloned().collect()
2027        };
2028        for client in &clients {
2029            if let Ok((bytes, _tx_id)) = client.create_destroy_packet_sync()
2030                && client.try_send_sync(&bytes)
2031            {
2032                trace!("TURN allocation destroy Refresh(LIFETIME=0) sent (best-effort)");
2033            }
2034        }
2035    }
2036
2037    pub fn stop(&self) {
2038        // Best-effort explicit TURN allocation destruction (RFC 5766 §7.4).
2039        // Spawned BEFORE flipping state to Closed so the TURN read loops that
2040        // dispatch the success/stale-nonce responses are still alive. The server
2041        // releases the allocation on receipt of Refresh(LIFETIME=0) regardless
2042        // of whether we observe the response, so this is robust even if the
2043        // detached task races the read-loop shutdown.
2044        self.destroy_turn_allocations_best_effort();
2045
2046        // Best-effort UPnP port mapping cleanup. Guarded: only spawn when a
2047        // tokio runtime is alive (normal close). During runtime teardown
2048        // (Drop) this is skipped — port mapping leases expire on the router.
2049        // Detached best-effort UPnP cleanup: only possible on a live tokio
2050        // runtime (normal close). During runtime teardown the handle is
2051        // unavailable and the mappings are abandoned (process is exiting).
2052        #[cfg(feature = "std")]
2053        if let Ok(handle) = tokio::runtime::Handle::try_current() {
2054            let upnp_clone = self.inner.gatherer.clone();
2055            handle.spawn(async move {
2056                upnp_clone.cleanup_upnp_mappings().await;
2057            });
2058        }
2059
2060        let _ = self.inner.set_state(IceTransportState::Closed);
2061        let _ = self.inner.selected_socket.send(None);
2062        let _ = self.inner.selected_rtcp_socket.send(None);
2063        let _ = self.inner.selected_pair_notifier.send(None);
2064        *self.inner.selected_pair.lock() = None;
2065        self.inner.gatherer.sockets.lock().clear();
2066        self.inner.gatherer.tcp_listeners.lock().clear();
2067        self.inner.gatherer.tcp_streams.lock().clear();
2068        self.inner.gatherer.shared_tcp_regs.lock().clear();
2069        self.inner.gatherer.shared_udp_regs.lock().clear();
2070        self.inner.gatherer.turn_clients.lock().clear();
2071        // Drop the shared-UDP handle so the demux port's per-session state is
2072        // released immediately instead of waiting for Arc<IceTransportInner>.
2073        *self.inner.gatherer.shared_udp_socket.lock() = None;
2074    }
2075
2076    /// Force the ICE transport into a specific state (test-only).
2077    ///
2078    /// Used to simulate ICE reconnect cycles in unit tests without running a
2079    /// full ICE stack.  Production code must never call this.
2080    #[cfg(test)]
2081    pub fn force_state_for_test(&self, state: IceTransportState) {
2082        let _ = self.inner.set_state(state);
2083    }
2084
2085    pub fn set_role(&self, role: IceRole) {
2086        *self.inner.role.lock() = role;
2087    }
2088
2089    pub fn add_remote_candidate(&self, candidate: IceCandidate) {
2090        let mut list = self.inner.remote_candidates.lock();
2091        list.push(candidate);
2092        drop(list);
2093        self.try_connectivity_checks();
2094    }
2095
2096    pub fn select_pair(&self, pair: IceCandidatePair) {
2097        *self.inner.selected_pair.lock() = Some(pair.clone());
2098        let _ = self.inner.selected_pair_notifier.send(Some(pair.clone()));
2099        if let Some(socket) = resolve_socket(&self.inner, &pair) {
2100            let _ = self.inner.selected_socket.send(Some(socket.clone()));
2101            publish_selected_rtcp_socket(&self.inner, Some(socket));
2102        }
2103        let _ = self.inner.set_state(IceTransportState::Connected);
2104    }
2105
2106    pub fn config(&self) -> &RtcConfiguration {
2107        &self.inner.config
2108    }
2109
2110    pub fn get_selected_socket(&self) -> Option<IceSocketWrapper> {
2111        if let Some(socket) = self.inner._socket_rx_keeper.borrow().clone() {
2112            return Some(socket);
2113        }
2114        let pair = self.inner.selected_pair.lock().clone()?;
2115        resolve_socket(&self.inner, &pair)
2116    }
2117
2118    pub fn get_selected_pair(&self) -> Option<IceCandidatePair> {
2119        self.inner.selected_pair.lock().clone()
2120    }
2121
2122    pub async fn set_data_receiver(&self, receiver: Arc<dyn PacketReceiver>) {
2123        {
2124            let mut rx_lock = self.inner.data_receiver.lock();
2125            *rx_lock = Some(receiver.clone());
2126        }
2127
2128        let packets: Vec<_> = {
2129            let mut buffer = self.inner.buffered_packets.lock();
2130            if buffer.is_empty() {
2131                return;
2132            }
2133            debug!(
2134                count = buffer.len(),
2135                "Flushing buffered RTP packets to newly registered data_receiver"
2136            );
2137            buffer.drain(..).collect()
2138        };
2139
2140        let mut marshal_buf = Vec::new();
2141        for (packet, addr) in packets {
2142            receiver
2143                .receive(Bytes::from(packet), addr, &mut marshal_buf)
2144                .await;
2145        }
2146    }
2147
2148    fn try_connectivity_checks(&self) {
2149        let _ = self.inner.cmd_tx.send(IceCommand::RunChecks);
2150    }
2151
2152    async fn handle_turn_packet(
2153        packet: &[u8],
2154        inner: &Arc<IceTransportInner>,
2155        client: &Arc<TurnClient>,
2156        relayed_addr: SocketAddr,
2157        marshal_buf: &mut Vec<u8>,
2158    ) {
2159        // Check for ChannelData (0x4000 - 0x7FFF)
2160        if packet.len() >= 4 {
2161            let channel_num = u16::from_be_bytes([packet[0], packet[1]]);
2162            if (0x4000..=0x7FFF).contains(&channel_num) {
2163                let len = u16::from_be_bytes([packet[2], packet[3]]) as usize;
2164                if packet.len() >= 4 + len {
2165                    let data = &packet[4..4 + len];
2166                    if let Some(peer_addr) = client.get_peer(channel_num).await {
2167                        handle_packet(
2168                            data,
2169                            peer_addr,
2170                            inner.clone(),
2171                            IceSocketWrapper::Turn(client.clone(), relayed_addr),
2172                            marshal_buf,
2173                        )
2174                        .await;
2175                    }
2176                }
2177                return;
2178            }
2179        }
2180
2181        if let Ok(msg) = StunMessage::decode(packet) {
2182            if msg.class == StunClass::Indication && msg.method == StunMethod::Data {
2183                if let Some(data) = &msg.data
2184                    && let Some(peer_addr) = msg.xor_peer_address
2185                {
2186                    handle_packet(
2187                        data,
2188                        peer_addr,
2189                        inner.clone(),
2190                        IceSocketWrapper::Turn(client.clone(), relayed_addr),
2191                        marshal_buf,
2192                    )
2193                    .await;
2194                }
2195            } else {
2196                // Handle other TURN messages (e.g. CreatePermission response)
2197                handle_packet(
2198                    packet,
2199                    relayed_addr,
2200                    inner.clone(),
2201                    IceSocketWrapper::Turn(client.clone(), relayed_addr),
2202                    marshal_buf,
2203                )
2204                .await;
2205            }
2206        }
2207    }
2208}
2209
2210async fn perform_connectivity_checks_async(inner: Arc<IceTransportInner>) {
2211    let state = *inner.state.borrow();
2212    if state != IceTransportState::Checking {
2213        return;
2214    }
2215
2216    // If we already have a selected pair, don't run more checks
2217    if inner.selected_pair.lock().is_some() {
2218        return;
2219    }
2220
2221    let remotes = inner.remote_candidates.lock().clone();
2222    let role = *inner.role.lock();
2223
2224    if remotes.is_empty() {
2225        return;
2226    }
2227
2228    let mut locals = inner.gatherer.local_candidates();
2229
2230    // Controlling agents may have no gathered locals when UDP is disabled and no TCP
2231    // passive port range is configured. Synthesize active TCP locals so we open
2232    // outbound connections to remote passive TCP candidates (RFC 6544).
2233    if locals.is_empty() && role == IceRole::Controlling {
2234        use core::net::{IpAddr, Ipv4Addr};
2235        for remote in &remotes {
2236            if remote.transport == "tcp" && remote.tcp_type == Some(TcpType::Passive) {
2237                locals.push(IceCandidate::tcp(
2238                    SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
2239                    remote.component,
2240                    "active",
2241                ));
2242            }
2243        }
2244    }
2245
2246    if locals.is_empty() {
2247        return;
2248    }
2249
2250    let mut pairs = Vec::new();
2251
2252    for local in &locals {
2253        for remote in &remotes {
2254            if local.transport != remote.transport {
2255                continue;
2256            }
2257            if local.component != remote.component {
2258                continue;
2259            }
2260            // Filter out Loopback -> Non-Loopback to avoid EADDRNOTAVAIL (os error 49)
2261            if local.address.ip().is_loopback() && !remote.address.ip().is_loopback() {
2262                continue;
2263            }
2264            if local.address.is_ipv4() != remote.address.is_ipv4() {
2265                continue;
2266            }
2267            // For Controlled role with TCP passive candidates, skip connectivity checks.
2268            // The pair will be selected when the Controlling side connects via TCP
2269            // and sends USE-CANDIDATE.
2270            if role == IceRole::Controlled
2271                && local.transport == "tcp"
2272                && local.tcp_type == Some(TcpType::Passive)
2273            {
2274                continue;
2275            }
2276            pairs.push(IceCandidatePair::new(local.clone(), remote.clone()));
2277        }
2278    }
2279
2280    // Sort by priority
2281    pairs.sort_by_key(|p| core::cmp::Reverse(p.priority(role)));
2282
2283    // If configured, demote host candidate pairs behind NAT so that srflx
2284    // pairs are checked first.  A host behind NAT may pass a single STUN
2285    // binding check but then fail the DTLS handshake, whereas the srflx
2286    // candidate (mapped public IP) works reliably.
2287    // Only demote when the remote is NOT also a private host — same-LAN pairs
2288    // (e.g. 192.168.1.x ↔ 192.168.1.y) keep their high priority.
2289    if inner.config.prefer_srflx_over_natted_host {
2290        let is_private_ip = |ip: core::net::IpAddr| -> bool {
2291            match ip {
2292                core::net::IpAddr::V4(v4) => v4.is_private(),
2293                core::net::IpAddr::V6(v6) => v6.is_unique_local(),
2294            }
2295        };
2296        let is_behind_nat = |pair: &IceCandidatePair| -> bool {
2297            pair.local.typ == IceCandidateType::Host
2298                && is_private_ip(pair.local.address.ip())
2299                && !(pair.remote.typ == IceCandidateType::Host
2300                    && is_private_ip(pair.remote.address.ip()))
2301        };
2302        pairs.sort_by(|a, b| {
2303            let a_natted = is_behind_nat(a);
2304            let b_natted = is_behind_nat(b);
2305            if a_natted != b_natted {
2306                return a_natted.cmp(&b_natted);
2307            }
2308            b.priority(role).cmp(&a.priority(role))
2309        });
2310    }
2311
2312    let mut pairs_to_check = Vec::new();
2313    {
2314        let mut checking = inner.checking_pairs.lock().await;
2315        for pair in pairs {
2316            let key = (pair.local.address, pair.remote.address);
2317            if !checking.contains(&key) {
2318                checking.insert(key);
2319                pairs_to_check.push(pair);
2320            }
2321        }
2322    }
2323
2324    if pairs_to_check.is_empty() {
2325        return;
2326    }
2327    let mut checks = futures::stream::FuturesUnordered::new();
2328
2329    for pair in pairs_to_check {
2330        let inner = inner.clone();
2331        let local = pair.local.clone();
2332        let remote = pair.remote.clone();
2333
2334        checks.push(async move {
2335            let key = (local.address, remote.address);
2336            let res = perform_binding_check(&local, &remote, &inner, role, false).await;
2337
2338            {
2339                let mut checking = inner.checking_pairs.lock().await;
2340                checking.remove(&key);
2341            }
2342
2343            match res {
2344                Ok(_) => Some(IceCandidatePair::new(local, remote)),
2345                Err(e) => {
2346                    // Per-pair failures are expected in mixed networks (unreachable
2347                    // LAN candidates, stale addresses, ...) — ICE keeps working via
2348                    // the other pairs. Keep them at trace to avoid log spam and tag
2349                    // with the configured label (call id) for correlation.
2350                    trace!(
2351                        label = inner.config.label.as_deref().unwrap_or("-"),
2352                        "ICE connectivity check failed: {} -> {}: {}",
2353                        local.address,
2354                        remote.address,
2355                        e
2356                    );
2357                    None
2358                }
2359            }
2360        });
2361    }
2362
2363    if checks.is_empty() {
2364        return;
2365    }
2366
2367    #[cfg(feature = "std")]
2368    use futures::stream::StreamExt;
2369    let mut successful_pairs: Vec<IceCandidatePair> = Vec::new();
2370
2371    // Collect successful pairs. Once the first usable pair arrives, only wait a
2372    // short grace window for additional (possibly higher-priority) pairs before
2373    // proceeding to nomination. This keeps a single unreachable candidate (e.g.
2374    // a stale LAN host candidate returning EHOSTDOWN/EHOSTUNREACH) from stalling
2375    // connection setup for the full STUN timeout — all checks still run
2376    // concurrently, but nomination is no longer gated on the slowest failing pair.
2377    const NOMINATION_GRACE: Duration = Duration::from_millis(200);
2378    loop {
2379        let next = if successful_pairs.is_empty() {
2380            checks.next().await
2381        } else {
2382            // checks arm polled first each wake (matches the old `biased;`).
2383            let mut checks_fut = core::pin::pin!(checks.next());
2384            let mut grace_fut = core::pin::pin!(crate::platform::task::sleep(NOMINATION_GRACE));
2385            match crate::platform::select::select2(&mut checks_fut, &mut grace_fut).await {
2386                crate::platform::select::Either::A(res) => res,
2387                crate::platform::select::Either::B(_) => break,
2388            }
2389        };
2390
2391        match next {
2392            Some(pair) => {
2393                if let Some(pair) = pair {
2394                    // Skip duplicates (same local+remote already collected)
2395                    let key = (pair.local.address, pair.remote.address);
2396                    if !successful_pairs
2397                        .iter()
2398                        .any(|p| (p.local.address, p.remote.address) == key)
2399                    {
2400                        successful_pairs.push(pair);
2401                    }
2402                }
2403            }
2404            None => break,
2405        }
2406    }
2407
2408    if successful_pairs.is_empty() {
2409        // Don't transition to Failed — trickle ICE candidates may arrive
2410        // later via add_remote_candidate and trigger new connectivity checks.
2411        // The disconnect monitor (ice_connection_timeout) handles long-term
2412        // connectivity failure.
2413        return;
2414    }
2415
2416    // Sort by priority: host > srflx > relay.  P2P first, relay last.
2417    successful_pairs.sort_by_key(|p| core::cmp::Reverse(p.priority(role)));
2418
2419    for p in &successful_pairs {
2420        debug!(
2421            label = inner.config.label.as_deref().unwrap_or("-"),
2422            "ICE successful pair ({}): local {} {:?} -> remote {} {:?}",
2423            if role == IceRole::Controlling {
2424                "controlling"
2425            } else {
2426                "controlled"
2427            },
2428            p.local.address,
2429            p.local.typ,
2430            p.remote.address,
2431            p.remote.typ
2432        );
2433    }
2434
2435    if role == IceRole::Controlling {
2436        // Signal Connected so the PeerConnection starts waiting for nomination_complete.
2437        let _ = inner.set_state(IceTransportState::Connected);
2438
2439        // Nominate once. Late trickle candidates re-trigger connectivity checks;
2440        // re-nominating on every round would make the controlled peer flap
2441        // between pairs (RFC 8445 nominates a single pair for the session —
2442        // re-nomination is only expected after an ICE restart, which resets
2443        // nomination state).
2444        if inner.nomination_complete.borrow().is_some() {
2445            debug!(
2446                label = inner.config.label.as_deref().unwrap_or("-"),
2447                "ICE checks complete (controlling): already nominated, keeping selected pair"
2448            );
2449            return;
2450        }
2451
2452        // RFC 8445 regular nomination: send USE-CANDIDATE on exactly ONE pair
2453        // — the highest-priority pair that passed connectivity checks
2454        // (`successful_pairs` is already sorted best-first). Nominating every
2455        // successful pair in parallel (the previous behaviour) sends
2456        // USE-CANDIDATE on multiple pairs, leaving the controlled peer unable
2457        // to tell which is authoritative; it had to freeze/guess, and when a
2458        // real controlling peer later re-nominated (e.g. a browser abandoning
2459        // a dead srflx path for its relay) the frozen side kept DTLS pointed
2460        // at the stale address and the handshake never completed. A single
2461        // authoritative nomination makes re-nomination unambiguous.
2462        //
2463        // Try pairs in priority order; a nomination binding check can still be
2464        // lost, so fall through to the next successful pair. Bound the whole
2465        // phase to a single nomination_timeout so a lossy link cannot multiply
2466        // the latency by the candidate count (which previously stalled setup
2467        // for N × nomination_timeout under high packet loss).
2468        let nomination_start = crate::platform::time::Instant::now();
2469        let mut nominated_pair: Option<IceCandidatePair> = None;
2470        for (idx, pair) in successful_pairs.iter().enumerate() {
2471            // Always attempt the best pair; only bound the fallbacks.
2472            if idx > 0
2473                && crate::platform::time::Instant::now().duration_since(nomination_start)
2474                    >= inner.config.nomination_timeout
2475            {
2476                debug!("Nomination deadline reached before trying all candidate pairs");
2477                break;
2478            }
2479            debug!(
2480                label = inner.config.label.as_deref().unwrap_or("-"),
2481                "Controlling agent nominating pair: {} -> {}",
2482                pair.local.address,
2483                pair.remote.address
2484            );
2485            match perform_binding_check(&pair.local, &pair.remote, &inner, role, true).await {
2486                Ok(_) => {
2487                    debug!(
2488                        label = inner.config.label.as_deref().unwrap_or("-"),
2489                        "Nomination succeeded: {} -> {}", pair.local.address, pair.remote.address
2490                    );
2491                    nominated_pair = Some(pair.clone());
2492                    break;
2493                }
2494                Err(e) => {
2495                    debug!(
2496                        label = inner.config.label.as_deref().unwrap_or("-"),
2497                        "Nomination failed for {} -> {}: {}",
2498                        pair.local.address,
2499                        pair.remote.address,
2500                        e
2501                    );
2502                }
2503            }
2504        }
2505
2506        // Fall back to the highest-priority pair on all-fail so best-effort
2507        // data still has a path.
2508        let final_pair = nominated_pair
2509            .clone()
2510            .unwrap_or_else(|| successful_pairs[0].clone());
2511        let nominated = nominated_pair.is_some();
2512        *inner.selected_pair.lock() = Some(final_pair.clone());
2513        let _ = inner.selected_pair_notifier.send(Some(final_pair.clone()));
2514        if let Some(socket) = resolve_socket(&inner, &final_pair) {
2515            let _ = inner.selected_socket.send(Some(socket.clone()));
2516            publish_selected_rtcp_socket(&inner, Some(socket));
2517        }
2518        debug!(
2519            label = inner.config.label.as_deref().unwrap_or("-"),
2520            "ICE checks complete. Selected pair: {} -> {}",
2521            final_pair.local.address,
2522            final_pair.remote.address
2523        );
2524
2525        if nominated {
2526            let _ = inner.nomination_complete.send(Some(true));
2527        } else {
2528            let _ = inner.nomination_complete.send(Some(false));
2529            let _ = inner.set_state(IceTransportState::Failed);
2530        }
2531    } else {
2532        // Controlled side: select best pair but don't nominate.
2533        // nomination_complete is signalled when we receive USE-CANDIDATE
2534        // from the controlling agent.
2535        //
2536        // Once the peer has nominated a pair, that choice is authoritative
2537        // and this selection must not run again: check rounds re-triggered
2538        // by late (e.g. peer-reflexive) candidates would otherwise stomp the
2539        // nominated pair with a locally-preferred one the peer never chose.
2540        if inner.nomination_complete.borrow().is_some() {
2541            debug!(
2542                label = inner.config.label.as_deref().unwrap_or("-"),
2543                "ICE checks complete (controlled): keeping peer-nominated pair"
2544            );
2545            return;
2546        }
2547        let pair = &successful_pairs[0];
2548        *inner.selected_pair.lock() = Some(pair.clone());
2549        let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2550        if let Some(socket) = resolve_socket(&inner, pair) {
2551            let _ = inner.selected_socket.send(Some(socket.clone()));
2552            publish_selected_rtcp_socket(&inner, Some(socket));
2553        }
2554        let _ = inner.set_state(IceTransportState::Connected);
2555        if pair.local.transport == "tcp" {
2556            let _ = inner.nomination_complete.send(Some(true));
2557        }
2558        debug!(
2559            label = inner.config.label.as_deref().unwrap_or("-"),
2560            "ICE checks complete. Selected pair: {} -> {}", pair.local.address, pair.remote.address
2561        );
2562
2563        // Controlled side with a working path but no USE-CANDIDATE yet:
2564        // the controlling agent's nomination is still in flight (our checks
2565        // complete before its do under asymmetric timing). Keep the selected
2566        // pair and wait, bounded by nomination_timeout — a dead controlling
2567        // agent still fails the transport via the PC-level timeout.
2568        if pair.local.transport != "tcp"
2569            && inner.nomination_complete.borrow().is_none()
2570        {
2571            debug!(
2572                label = inner.config.label.as_deref().unwrap_or("-"),
2573                "Controlled checks complete without nomination — waiting for USE-CANDIDATE"
2574            );
2575            let deadline = Instant::now() + inner.config.nomination_timeout;
2576            while Instant::now() < deadline
2577                && inner.nomination_complete.borrow().is_none()
2578                && inner.state.borrow().clone() == IceTransportState::Connected
2579            {
2580                crate::platform::task::sleep(core::time::Duration::from_millis(20)).await;
2581            }
2582            if inner.nomination_complete.borrow().is_none() {
2583                debug!(
2584                    label = inner.config.label.as_deref().unwrap_or("-"),
2585                    "Nomination did not arrive in time; keeping best-effort selected pair"
2586                );
2587                let _ = inner.nomination_complete.send(Some(true));
2588            }
2589        }
2590    }
2591}
2592
2593fn resolve_socket(inner: &IceTransportInner, pair: &IceCandidatePair) -> Option<IceSocketWrapper> {
2594    if pair.local.typ == IceCandidateType::Relay {
2595        let clients = inner.gatherer.turn_clients.lock();
2596        clients
2597            .get(&pair.local.address)
2598            .map(|c| IceSocketWrapper::Turn(c.clone(), pair.local.address))
2599    } else if pair.local.transport == "tcp" {
2600        // Prefer the accepted inbound stream that matches the nominated remote peer.
2601        // get_tcp_socket() keys by listener local_addr and may return a stale socket
2602        // when multiple sessions share a passive port range.
2603        let streams = inner.gatherer.tcp_streams.lock();
2604        for wrapper in streams.values() {
2605            #[cfg(feature = "std")]
2606            if let IceSocketWrapper::TcpStream(_, _, peer) = wrapper
2607                && *peer == pair.remote.address
2608            {
2609                return Some(wrapper.clone());
2610            }
2611        }
2612        drop(streams);
2613        inner.gatherer.get_tcp_socket(pair.local.base_address())
2614    } else {
2615        // Shared UDP mux socket backs the single host candidate when active.
2616        if pair.local.typ == IceCandidateType::Host
2617            && let Some(shared) = inner.gatherer.shared_udp_socket.lock().clone()
2618        {
2619            return Some(shared);
2620        }
2621        // Factory-bound platform sockets (rtcembed / loopback backends).
2622        if let Some(platform) = inner.gatherer.get_platform_socket(pair.local.base_address()) {
2623            return Some(IceSocketWrapper::Platform(platform));
2624        }
2625        let socket = inner.gatherer.get_socket(pair.local.base_address());
2626        if socket.is_none() {
2627            debug!(
2628                "resolve_socket: failed to find socket for {}",
2629                pair.local.base_address()
2630            );
2631        }
2632        socket.map(IceSocketWrapper::Udp)
2633    }
2634}
2635
2636fn publish_selected_socket(
2637    inner: &IceTransportInner,
2638    pair: &IceCandidatePair,
2639    inbound: Option<&IceSocketWrapper>,
2640) {
2641    // Inbound TCP is authoritative for passive ICE-TCP: the controlling peer
2642    // connected to us on this stream and nominated it via USE-CANDIDATE.
2643    let socket = match inbound {
2644        #[cfg(feature = "std")]
2645        Some(s @ IceSocketWrapper::TcpStream(_, _, _)) => Some(s.clone()),
2646        _ => resolve_socket(inner, pair),
2647    };
2648    if let Some(socket) = socket {
2649        #[cfg(feature = "std")]
2650        let inbound_tcp = matches!(inbound, Some(IceSocketWrapper::TcpStream(_, _, _)));
2651        #[cfg(not(feature = "std"))]
2652        let inbound_tcp = false;
2653        debug!(
2654            pair_local = %pair.local.address,
2655            pair_remote = %pair.remote.address,
2656            socket = %socket.diag(),
2657            inbound_tcp,
2658            "ICE: published selected socket"
2659        );
2660        let _ = inner.selected_socket.send(Some(socket.clone()));
2661        publish_selected_rtcp_socket(inner, Some(socket));
2662    }
2663}
2664
2665/// Apply a verified controlled-side nomination only if it is still the most
2666/// recent one. `generation` is the value captured when the USE-CANDIDATE that
2667/// spawned the verification was handled; any newer nomination (or an ICE
2668/// restart) bumps `nomination_generation`, in which case this returns `false`
2669/// and leaves the currently selected pair untouched.
2670fn commit_verified_nomination(
2671    inner: &IceTransportInner,
2672    generation: u64,
2673    pair: &IceCandidatePair,
2674    sender: &IceSocketWrapper,
2675) -> bool {
2676    if inner
2677        .nomination_generation
2678        .load(core::sync::atomic::Ordering::SeqCst)
2679        != generation
2680    {
2681        return false;
2682    }
2683    *inner.selected_pair.lock() = Some(pair.clone());
2684    let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2685    publish_selected_socket(inner, pair, Some(sender));
2686    true
2687}
2688
2689#[cfg(feature = "std")]
2690async fn complete_controlled_inbound_tcp_nomination(
2691    sender: &IceSocketWrapper,
2692    addr: SocketAddr,
2693    inner: Arc<IceTransportInner>,
2694) {
2695    if *inner.role.lock() != IceRole::Controlled {
2696        return;
2697    }
2698    #[cfg(feature = "std")]
2699    let IceSocketWrapper::TcpStream(read, _, _) = sender else {
2700        return;
2701    };
2702    if inner.nomination_complete.borrow().is_some() {
2703        if let Some(pair) = inner.selected_pair.lock().clone() {
2704            publish_selected_socket(&inner, &pair, Some(sender));
2705        }
2706        return;
2707    }
2708
2709    let local_addr: SocketAddr = {
2710        let s = read.lock().await;
2711        s.local_addr()
2712            .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
2713    };
2714
2715    let locals = inner.gatherer.local_candidates();
2716    let local_cand = locals.iter().find(|c| {
2717        c.base_address() == local_addr
2718            || (c.transport == "tcp"
2719                && c.base_address().port() == local_addr.port()
2720                && (c.base_address().ip().is_unspecified() || local_addr.ip().is_unspecified()))
2721    });
2722
2723    let pair = {
2724        let remotes = inner.remote_candidates.lock();
2725        let remote_cand = remotes.iter().find(|c| c.address == addr);
2726        if let (Some(l), Some(r)) = (local_cand, remote_cand) {
2727            Some(IceCandidatePair::new(l.clone(), r.clone()))
2728        } else {
2729            None
2730        }
2731    };
2732
2733    if let Some(pair) = pair {
2734        trace!(
2735            "Controlled agent selected pair via inbound TCP nomination: {} -> {}",
2736            pair.local.address, pair.remote.address
2737        );
2738        // A TCP nomination supersedes any pending UDP path verification.
2739        inner
2740            .nomination_generation
2741            .fetch_add(1, core::sync::atomic::Ordering::SeqCst);
2742        *inner.selected_pair.lock() = Some(pair.clone());
2743        let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2744        publish_selected_socket(&inner, &pair, Some(sender));
2745        let _ = inner.set_state(IceTransportState::Connected);
2746    } else {
2747        debug!(
2748            "Inbound TCP nomination: synthesizing pair for {} -> {}",
2749            local_addr, addr
2750        );
2751        let local_cand = locals.iter().find(|c| {
2752            c.transport == "tcp"
2753                && c.tcp_type == Some(TcpType::Passive)
2754                && (c.base_address().port() == local_addr.port()
2755                    || c.address.port() == local_addr.port())
2756        });
2757        let remote_cand = {
2758            let remotes = inner.remote_candidates.lock();
2759            remotes.iter().find(|c| c.address == addr).cloned()
2760        };
2761        if let (Some(l), Some(r)) = (local_cand, remote_cand) {
2762            let pair = IceCandidatePair::new(l.clone(), r.clone());
2763            *inner.selected_pair.lock() = Some(pair.clone());
2764            let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
2765            publish_selected_socket(&inner, &pair, Some(sender));
2766            let _ = inner.set_state(IceTransportState::Connected);
2767        } else {
2768            let _ = inner.selected_socket.send(Some(sender.clone()));
2769            publish_selected_rtcp_socket(&inner, Some(sender.clone()));
2770        }
2771    }
2772    let _ = inner.nomination_complete.send(Some(true));
2773    let pair_summary = inner
2774        .selected_pair
2775        .lock()
2776        .as_ref()
2777        .map(|p| format!("{} -> {}", p.local.address, p.remote.address))
2778        .unwrap_or_else(|| format!("(no pair) peer={addr}"));
2779    debug!(
2780        peer = %addr,
2781        local_bind = %local_addr,
2782        pair = %pair_summary,
2783        socket = %sender.diag(),
2784        "ICE: passive TCP nomination complete"
2785    );
2786}
2787
2788fn resolve_rtcp_socket(inner: &IceTransportInner) -> Option<IceSocketWrapper> {
2789    let candidate = inner
2790        .gatherer
2791        .local_candidates()
2792        .into_iter()
2793        .find(|candidate| candidate.component == 2)?;
2794
2795    if candidate.typ == IceCandidateType::Relay {
2796        let clients = inner.gatherer.turn_clients.lock();
2797        clients
2798            .get(&candidate.address)
2799            .map(|client| IceSocketWrapper::Turn(client.clone(), candidate.address))
2800    } else if candidate.transport == "tcp" {
2801        inner.gatherer.get_tcp_socket(candidate.base_address())
2802    } else {
2803        // Factory-bound platform sockets (rtcembed / loopback backends).
2804        if let Some(platform) = inner.gatherer.get_platform_socket(candidate.base_address()) {
2805            return Some(IceSocketWrapper::Platform(platform));
2806        }
2807        let socket = inner.gatherer.get_socket(candidate.base_address());
2808        if socket.is_none() {
2809            debug!(
2810                "resolve_rtcp_socket: failed to find socket for {}",
2811                candidate.base_address()
2812            );
2813        }
2814        socket.map(IceSocketWrapper::Udp)
2815    }
2816}
2817
2818fn publish_selected_rtcp_socket(inner: &IceTransportInner, fallback: Option<IceSocketWrapper>) {
2819    if let Some(socket) = resolve_rtcp_socket(inner).or(fallback) {
2820        let _ = inner.selected_rtcp_socket.send(Some(socket));
2821    }
2822}
2823
2824async fn bind_direct_rtcp_socket(
2825    inner: &IceTransportInner,
2826    rtp_base: SocketAddr,
2827    advertised_ip: IpAddr,
2828) -> RtcResult<(IceSocketWrapper, IceCandidate)> {
2829    let rtcp_bind_addr = rtp_base
2830        .port()
2831        .checked_add(1)
2832        .map(|port| SocketAddr::new(rtp_base.ip(), port));
2833    let rtcp = if let Some(addr) = rtcp_bind_addr {
2834        match inner.gatherer.bind_one(addr).await {
2835            Ok(socket) => socket,
2836            Err(err) => {
2837                debug!(
2838                    "Failed to bind RTCP socket on {}, falling back to ephemeral port: {}",
2839                    addr, err
2840                );
2841                inner
2842                    .gatherer
2843                    .bind_one(SocketAddr::new(rtp_base.ip(), 0))
2844                    .await?
2845            }
2846        }
2847    } else {
2848        inner
2849            .gatherer
2850            .bind_one(SocketAddr::new(rtp_base.ip(), 0))
2851            .await?
2852    };
2853    let local_rtcp_addr = rtcp.local_addr()?;
2854    inner.gatherer.register_bound(&rtcp);
2855
2856    let mut rtcp_cand_addr = local_rtcp_addr;
2857    rtcp_cand_addr.set_ip(advertised_ip);
2858    let mut candidate = IceCandidate::host(rtcp_cand_addr, 2);
2859    if rtcp_cand_addr != local_rtcp_addr {
2860        candidate.related_address = Some(local_rtcp_addr);
2861    }
2862    Ok((rtcp, candidate))
2863}
2864
2865
2866async fn handle_packet(
2867    packet: &[u8],
2868    addr: SocketAddr,
2869    inner: Arc<IceTransportInner>,
2870    sender: IceSocketWrapper,
2871    marshal_buf: &mut Vec<u8>,
2872) {    if should_drop_packet() {
2873        return;
2874    }
2875    inner.last_received_nanos.store(
2876        inner.created_at.elapsed().as_nanos() as u64,
2877        Ordering::Relaxed,
2878    );
2879    let b = packet[0];
2880    if b < 2 {
2881        // STUN
2882        match StunMessage::decode(packet) {
2883            Ok(msg) => {
2884                if msg.class == StunClass::Request {
2885                    // Always respond to STUN Binding Requests on any transport mode
2886                    // (RFC 5389 compliance). Sending a Binding Response lets the remote
2887                    // peer (e.g. Linphone) confirm the media port is reachable even when
2888                    // it sends a STUN probe before its first real RTP packet.
2889                    //
2890                    // Address latching (updating the selected pair's remote IP based on
2891                    // the incoming source) is a separate concern gated by
2892                    // `enable_latching` inside handle_stun_request — it is NOT the same
2893                    // as "should we even reply to this STUN message".
2894                    handle_stun_request(&sender, &msg, addr, inner).await;
2895                } else if msg.class == StunClass::SuccessResponse {
2896                    let mut map = inner.pending_transactions.lock();
2897                    if let Some(tx) = map.remove(&msg.transaction_id) {
2898                        let _ = tx.send(msg);
2899                    } else {
2900                        trace!(
2901                            "Unmatched transaction {:?} Pending transactions: {:?}",
2902                            msg.transaction_id,
2903                            map.keys()
2904                        );
2905                    }
2906                } else if msg.class == StunClass::ErrorResponse {
2907                    trace!("Received STUN Error Response from {}", addr);
2908                    debug!(
2909                        "Received STUN Error Response from {}: {:?}",
2910                        addr, msg.error_code
2911                    );
2912                    if let Some(code) = msg.error_code {
2913                        if code == 401 {
2914                            let remote_params = inner.remote_parameters.lock().clone();
2915                            debug!(
2916                                "STUN 401 received. Current remote params: {:?}",
2917                                remote_params
2918                            );
2919                        }
2920                        trace!("Error code: {}", code);
2921                    }
2922                    // Dispatch error responses to pending transactions so that callers
2923                    // waiting on send_and_await_inner (e.g. TURN Refresh, ChannelBind)
2924                    // can receive 401/438 and retry with a fresh nonce instead of timing out.
2925                    let mut map = inner.pending_transactions.lock();
2926                    if let Some(tx) = map.remove(&msg.transaction_id) {
2927                        let _ = tx.send(msg);
2928                    }
2929                }
2930            }
2931            Err(e) => {
2932                debug!("Failed to decode STUN packet from {}: {}", addr, e);
2933            }
2934        }
2935    } else {
2936        // DTLS or RTP
2937        let receiver = inner.data_receiver.lock().clone();
2938        if let Some(rx) = receiver {
2939            rx.receive(Bytes::copy_from_slice(packet), addr, marshal_buf)
2940                .await;
2941        } else {
2942            let mut buffer = inner.buffered_packets.lock();
2943            let stats = inner.buffer_stats.clone();
2944            let capacity = inner.config.rtp_buffer_capacity;
2945
2946            stats.packets_received.fetch_add(1, Ordering::Relaxed);
2947
2948            if buffer.len() >= capacity {
2949                match inner.config.buffer_drop_strategy {
2950                    BufferDropStrategy::DropOldest => {
2951                        buffer.pop_front();
2952                        buffer.push_back((packet.to_vec(), addr));
2953                    }
2954                    BufferDropStrategy::DropNew => {
2955                        // Rate-limit: warn on the first drop and every 1000th
2956                        // thereafter, so a persistently-full buffer does not
2957                        // spam per packet (drops are also counted in buffer_stats).
2958                        let dropped = stats.packets_dropped.load(Ordering::Relaxed);
2959                        if dropped == 0 || dropped.is_multiple_of(1000) {
2960                            tracing::warn!(src = %addr, capacity, "RTP buffer full — dropping inbound packet (DropNew strategy)");
2961                        }
2962                    }
2963                }
2964                stats.packets_dropped.fetch_add(1, Ordering::Relaxed);
2965            } else {
2966                buffer.push_back((packet.to_vec(), addr));
2967            }
2968
2969            // Update statistics
2970            let current_size = buffer.len() as u32;
2971            stats.current_size.store(current_size, Ordering::Relaxed);
2972
2973            // Track peak size
2974            let mut peak = stats.peak_size.load(Ordering::Relaxed);
2975            while current_size > peak {
2976                match stats.peak_size.compare_exchange_weak(
2977                    peak,
2978                    current_size,
2979                    Ordering::Relaxed,
2980                    Ordering::Relaxed,
2981                ) {
2982                    Ok(_) => break,
2983                    Err(current) => peak = current,
2984                }
2985            }
2986
2987            // Periodic logging
2988            let mut last_log = stats.last_log_time.lock();
2989            if last_log.elapsed() >= inner.config.buffer_stats_log_interval {
2990                let received = stats.packets_received.load(Ordering::Relaxed);
2991                let dropped = stats.packets_dropped.load(Ordering::Relaxed);
2992                let peak_size = stats.peak_size.load(Ordering::Relaxed);
2993                trace!(
2994                    "Buffer stats: received={}, dropped={}, current={}, peak={}, capacity={}",
2995                    received, dropped, current_size, peak_size, capacity
2996                );
2997                *last_log = Instant::now();
2998            }
2999        }
3000    }
3001}
3002
3003/// Round-trip a binding request on a newly nominated path. Returns true when
3004/// the peer answers on that path, proving media can actually flow there.
3005///
3006/// Used by the Controlled UseCandidate handler: once a session is already
3007/// running on a nominated pair, a second nomination must prove the new path
3008/// is live (the controlling peer answers checks on a path it truly switched
3009/// to) before the SRTP downlink is diverted onto it.
3010async fn verify_nominated_path(
3011    sender: &IceSocketWrapper,
3012    addr: SocketAddr,
3013    inner: Arc<IceTransportInner>,
3014) -> bool {
3015    let remote_params = inner.remote_parameters.lock().clone();
3016    let Some(rp) = remote_params else {
3017        return false;
3018    };
3019    let local_params = inner.local_parameters.lock().clone();
3020
3021    let tx_id = random_bytes::<12>();
3022    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
3023    msg.attributes.push(StunAttribute::Username(format!(
3024        "{}:{}",
3025        rp.username_fragment, local_params.username_fragment
3026    )));
3027    let bytes = match msg.encode(Some(rp.password.as_bytes()), true) {
3028        Ok(b) => b,
3029        Err(_) => return false,
3030    };
3031
3032    let (tx, rx) = oneshot::channel();
3033    {
3034        let mut map = inner.pending_transactions.lock();
3035        map.insert(tx_id, tx);
3036    }
3037
3038    if let Err(e) = sender.send_to(&bytes, addr).await {
3039        inner.pending_transactions.lock().remove(&tx_id);
3040        debug!("Path verification send to {} failed: {}", addr, e);
3041        return false;
3042    }
3043
3044    let verified = with_timeout(inner.config.stun_timeout, rx)
3045        .await
3046        .map(|res| res.is_ok())
3047        .unwrap_or(false);
3048    if !verified {
3049        let mut map = inner.pending_transactions.lock();
3050        map.remove(&tx_id);
3051    }
3052    verified
3053}
3054
3055async fn handle_stun_request(
3056    sender: &IceSocketWrapper,
3057    msg: &StunDecoded,
3058    addr: SocketAddr,
3059    inner: Arc<IceTransportInner>,
3060) {
3061    let response = StunMessage::binding_success_response(msg.transaction_id, addr);
3062
3063    #[cfg(any(test, feature = "simulator"))]
3064    simulate_stun_respond_delay(sender).await;
3065
3066    let password = inner.local_parameters.lock().password.clone();
3067    if let Ok(bytes) = response.encode(Some(password.as_bytes()), true) {
3068        match sender.send_to(&bytes, addr).await {
3069            Ok(_) => trace!("Sent STUN Response to {}", addr),
3070            Err(e) => {
3071                // send failures (incl. unreachable/err65/49) are logged and
3072                // tolerated — STUN retransmits recover
3073                debug!("Failed to send STUN Response to {}: {}", addr, e);
3074            }
3075        }
3076    } else {
3077        debug!("Failed to encode STUN Response");
3078    }
3079
3080    // Check if we know this candidate
3081    let mut known = false;
3082    {
3083        let remotes = inner.remote_candidates.lock();
3084        for cand in remotes.iter() {
3085            if cand.address == addr {
3086                known = true;
3087                break;
3088            }
3089        }
3090    }
3091
3092    if !known {
3093        debug!("Discovered peer reflexive candidate: {}", addr);
3094        let transport = match sender {
3095            IceSocketWrapper::Platform(_)
3096            | IceSocketWrapper::Udp(_)
3097            | IceSocketWrapper::SharedUdp(_) => "udp",
3098            #[cfg(feature = "std")]
3099            IceSocketWrapper::TcpListener(_) | IceSocketWrapper::TcpStream(_, _, _) => "tcp",
3100            IceSocketWrapper::Turn(_, _) => "udp",
3101        };
3102        let mut candidate = IceCandidate::host(addr, 1); // Use host for now, or prflx
3103        candidate.typ = IceCandidateType::PeerReflexive;
3104        candidate.transport = transport.to_string();
3105        candidate.foundation = IceCandidate::compute_foundation(
3106            IceCandidateType::PeerReflexive,
3107            candidate.base_address(),
3108            transport,
3109        );
3110        candidate.priority = if transport == "tcp" {
3111            IceCandidate::priority_for_tcp(IceCandidateType::PeerReflexive, 1, TcpType::Passive)
3112        } else {
3113            IceCandidate::priority_for(IceCandidateType::PeerReflexive, 1)
3114        };
3115
3116        let mut list = inner.remote_candidates.lock();
3117        list.push(candidate);
3118        drop(list);
3119
3120        let _ = inner.cmd_tx.send(IceCommand::RunChecks);
3121    }
3122    // ICE-layer latching retargets the selected pair when an inbound STUN
3123    // arrives from the same port on a different IP. That is an RTP/SRTP-mode
3124    // concept (plain RTP peers may legitimately change source addresses
3125    // mid-stream). For WebRTC the SRTP media path must stay pinned to the
3126    // nominated pair: retargeting it onto an address the peer never nominated
3127    // blackholes media while consent keepalives still flow on the original
3128    // socket.
3129    if inner.config.enable_latching && inner.config.transport_mode != crate::TransportMode::WebRtc {
3130        let current_pair = inner.selected_pair.lock().clone();
3131        if let Some(pair) = current_pair
3132            && pair.remote.address.port() == addr.port()
3133            && pair.remote.address.ip() != addr.ip()
3134        {
3135            debug!(
3136                "RTP latching: updating remote address from {} to {}",
3137                pair.remote.address, addr
3138            );
3139            let mut new_remote = pair.remote.clone();
3140            new_remote.address = addr;
3141            let new_pair = IceCandidatePair::new(pair.local.clone(), new_remote);
3142            *inner.selected_pair.lock() = Some(new_pair.clone());
3143            let _ = inner.selected_pair_notifier.send(Some(new_pair.clone()));
3144            publish_selected_socket(&inner, &new_pair, Some(sender));
3145        }
3146    }
3147
3148    #[cfg(feature = "std")]
3149    complete_controlled_inbound_tcp_nomination(sender, addr, inner.clone()).await;
3150    #[cfg(not(feature = "std"))]
3151    {
3152        let _ = (sender, &addr, &inner);
3153    }
3154
3155    if msg.use_candidate {
3156        let role = *inner.role.lock();
3157        if role == IceRole::Controlled {
3158            // TCP passive nomination is handled above; UDP still uses USE-CANDIDATE below.
3159            #[cfg(feature = "std")]
3160            if matches!(sender, IceSocketWrapper::TcpStream(_, _, _)) {
3161                #[allow(unused_mut, unused_variables)]
3162                let sender = &sender;
3163                return;
3164            }
3165            // A newer USE-CANDIDATE supersedes any pending path verification:
3166            // the most recent nomination wins (RFC 8445 §8.1.1). Capture this
3167            // nomination's generation so a deferred verifier can detect that it
3168            // has since been superseded and must not move media (issue #55).
3169            let use_candidate_generation = inner
3170                .nomination_generation
3171                .fetch_add(1, core::sync::atomic::Ordering::SeqCst)
3172                + 1;
3173            // The controlling agent is authoritative, but "authoritative"
3174            // does not mean media should be diverted onto an unproven path.
3175            // Two failure modes motivated the current design:
3176            //
3177            //   * Freezing on the first nomination (very old behaviour) broke
3178            //     legitimate browser path-failover: a browser whose srflx path
3179            //     died re-sends USE-CANDIDATE on its relay path, and a frozen
3180            //     controlled agent kept sending DTLS to the abandoned address.
3181            //   * Following every USE-CANDIDATE unconditionally let duplicate
3182            //     or oscillating nominations (e.g. a browser nominating both
3183            //     multi-homed host candidates of the server) divert the SRTP
3184            //     downlink onto a path the peer never sends media on, so the
3185            //     call keeps "flowing" on PBX counters while the user hears
3186            //     nothing.
3187            //
3188            // The current behaviour follows the RFC 8445 intent while staying
3189            // safe: the FIRST nomination is followed immediately; a later
3190            // nomination on a different pair must pass a same-path round trip
3191            // (see `verify_nominated_path`) before media is switched. Genuine
3192            // failovers answer checks on the new path and converge within one
3193            // RTT; dead paths never respond and are ignored.
3194            let local_addr: SocketAddr = match sender {
3195                IceSocketWrapper::Platform(s) => s
3196                    .local_addr()
3197                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
3198                IceSocketWrapper::Udp(s) => s
3199                    .local_addr()
3200                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
3201                IceSocketWrapper::SharedUdp(h) => h
3202                    .local_addr()
3203                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
3204                #[cfg(feature = "std")]
3205                IceSocketWrapper::TcpListener(l) => l
3206                    .local_addr()
3207                    .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap()),
3208                #[cfg(feature = "std")]
3209                IceSocketWrapper::TcpStream(read, _, _) => {
3210                    let s = read.lock().await;
3211                    s.local_addr()
3212                        .unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
3213                }
3214                IceSocketWrapper::Turn(_, addr) => *addr,
3215            };
3216
3217            let locals = inner.gatherer.local_candidates();
3218            let local_cand = locals.iter().find(|c| c.base_address() == local_addr);
3219
3220            let pair = {
3221                let remotes = inner.remote_candidates.lock();
3222                let remote_cand = remotes.iter().find(|c| c.address == addr);
3223                if let (Some(l), Some(r)) = (local_cand, remote_cand) {
3224                    Some(IceCandidatePair::new(l.clone(), r.clone()))
3225                } else {
3226                    None
3227                }
3228            };
3229
3230            if let Some(pair) = pair {
3231                let same_pair = {
3232                    let selected = inner.selected_pair.lock();
3233                    selected
3234                        .as_ref()
3235                        .map(|cur| {
3236                            cur.local.address == pair.local.address
3237                                && cur.remote.address == pair.remote.address
3238                        })
3239                        .unwrap_or(false)
3240                };
3241                let has_selected = inner.selected_pair.lock().is_some();
3242
3243                if same_pair {
3244                    trace!(
3245                        "Controlled agent keeping current pair (UseCandidate {} -> {})",
3246                        pair.local.address, pair.remote.address
3247                    );
3248                } else if !has_selected {
3249                    // First nomination: follow immediately so call setup is
3250                    // never delayed.
3251                    debug!(
3252                        label = inner.config.label.as_deref().unwrap_or("-"),
3253                        "Controlled agent following UseCandidate (first nomination): {} -> {}",
3254                        pair.local.address,
3255                        pair.remote.address
3256                    );
3257                    *inner.selected_pair.lock() = Some(pair.clone());
3258                    let _ = inner.selected_pair_notifier.send(Some(pair.clone()));
3259                    publish_selected_socket(&inner, &pair, Some(sender));
3260                } else {
3261                    // A second nomination on a DIFFERENT pair while already
3262                    // connected: verify the new path with a same-path round
3263                    // trip before diverting media to it. A controlling peer
3264                    // that genuinely switched (dead path → relay failover)
3265                    // answers checks on the new path; duplicate or oscillating
3266                    // nominations on dead paths never respond and are ignored,
3267                    // keeping the working path (and its established DTLS/SRTP
3268                    // session) intact.
3269                    debug!(
3270                        label = inner.config.label.as_deref().unwrap_or("-"),
3271                        "Controlled agent deferring switch to newly nominated path {} -> {} until verified",
3272                        pair.local.address,
3273                        pair.remote.address
3274                    );
3275                    let inner2 = inner.clone();
3276                    let sender2 = sender.clone();
3277                    let pair2 = pair.clone();
3278                    crate::platform::task::spawn(async move {
3279                        let verified = verify_nominated_path(&sender2, addr, inner2.clone()).await;
3280                        if !verified {
3281                            debug!(
3282                                label = inner2.config.label.as_deref().unwrap_or("-"),
3283                                "New nominated path {} -> {} failed verification; keeping current pair",
3284                                pair2.local.address,
3285                                pair2.remote.address
3286                            );
3287                        } else if commit_verified_nomination(
3288                            &inner2,
3289                            use_candidate_generation,
3290                            &pair2,
3291                            &sender2,
3292                        ) {
3293                            debug!(
3294                                label = inner2.config.label.as_deref().unwrap_or("-"),
3295                                "New nominated path verified, switching: {} -> {}",
3296                                pair2.local.address,
3297                                pair2.remote.address
3298                            );
3299                        } else {
3300                            debug!(
3301                                label = inner2.config.label.as_deref().unwrap_or("-"),
3302                                "New nominated path {} -> {} verified but superseded by a newer nomination; keeping current pair",
3303                                pair2.local.address,
3304                                pair2.remote.address
3305                            );
3306                        }
3307                    });
3308                }
3309                let _ = inner.set_state(IceTransportState::Connected);
3310                let _ = inner.nomination_complete.send(Some(true));
3311            } else {
3312                debug!(
3313                    "Received UseCandidate but could not find UDP pair for {} -> {}",
3314                    local_addr, addr
3315                );
3316                let _ = inner.nomination_complete.send(Some(true));
3317            }
3318        }
3319    }
3320}
3321
3322struct TransactionGuard<'a> {
3323    map: &'a crate::platform::sync::Mutex<BTreeMap<[u8; 12], oneshot::Sender<StunDecoded>>>,
3324    tx_id: [u8; 12],
3325}
3326
3327impl<'a> Drop for TransactionGuard<'a> {
3328    fn drop(&mut self) {
3329        // debug!("TransactionGuard: dropping tx={:?}", self.tx_id);
3330        let mut map = self.map.lock();
3331        map.remove(&self.tx_id);
3332    }
3333}
3334
3335async fn perform_binding_check(
3336    local: &IceCandidate,
3337    remote: &IceCandidate,
3338    inner: &Arc<IceTransportInner>,
3339    role: IceRole,
3340    nominated: bool,
3341) -> RtcResult<()> {
3342    // Handle TCP candidates separately — establish connection and perform STUN over TCP
3343    #[cfg(feature = "std")]
3344    if local.transport == "tcp" && remote.transport == "tcp" {
3345        return perform_tcp_binding_check(local, remote, inner, role, nominated).await;
3346    }
3347    #[cfg(not(feature = "std"))]
3348    if local.transport == "tcp" && remote.transport == "tcp" {
3349        return Err(RtcError::Internal(
3350            "ICE-TCP is excluded from the embedded target".into(),
3351        ));
3352    }
3353
3354    // For Controlled role with TCP passive candidates, don't initiate outbound checks
3355    if role == IceRole::Controlled && local.transport == "tcp" {
3356        return Ok(());
3357    }
3358
3359    // For non-TCP candidates, transport must be UDP
3360    if remote.transport != "udp" {
3361        return Err(RtcError::Internal(format!(
3362            "only UDP connectivity checks are supported"
3363        )));
3364    }
3365
3366    let local_params = inner.local_parameters.lock().clone();
3367    let remote_params = match inner.remote_parameters.lock().clone() {
3368        Some(p) => p,
3369        None => return Err(RtcError::Internal(format!("no remote params"))),
3370    };
3371
3372    let tx_id = random_bytes::<12>();
3373    // debug!("perform_binding_check: starting check for {} -> {} tx={:?}", local.address, remote.address, tx_id);
3374
3375    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
3376    let username = format!(
3377        "{}:{}",
3378        remote_params.username_fragment, local_params.username_fragment
3379    );
3380    msg.attributes.push(StunAttribute::Username(username));
3381    msg.attributes.push(StunAttribute::Priority(local.priority));
3382    match role {
3383        IceRole::Controlling => {
3384            msg.attributes
3385                .push(StunAttribute::IceControlling(local_params.tie_breaker));
3386            if nominated {
3387                msg.attributes.push(StunAttribute::UseCandidate);
3388            }
3389        }
3390        IceRole::Controlled => msg
3391            .attributes
3392            .push(StunAttribute::IceControlled(local_params.tie_breaker)),
3393    }
3394    let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
3395
3396    let (tx, mut rx) = oneshot::channel();
3397    {
3398        let mut map = inner.pending_transactions.lock();
3399        map.insert(tx_id, tx);
3400    }
3401
3402    // Ensure transaction is removed when this future is dropped
3403    let _guard = TransactionGuard {
3404        map: &inner.pending_transactions,
3405        tx_id,
3406    };
3407
3408    let (socket, platform_socket, turn_client) = if local.typ == IceCandidateType::Relay {
3409        let gatherer = &inner.gatherer;
3410        let clients = gatherer.turn_clients.lock();
3411        let client = clients.get(&local.address).cloned();
3412        (None, None, client)
3413    } else {
3414        let socket = inner.gatherer.get_socket(local.base_address());
3415        let platform_socket = inner.gatherer.get_platform_socket(local.base_address());
3416        (socket, platform_socket, None)
3417    };
3418
3419    if local.typ == IceCandidateType::Relay {
3420        let client = turn_client.as_ref().ok_or_else(|| {
3421            RtcError::Internal(format!("TURN client not found for relay candidate"))
3422        })?;
3423
3424        let (perm_bytes, perm_tx_id) = client.create_permission_packet(remote.address).await?;
3425
3426        let (perm_tx, perm_rx) = oneshot::channel();
3427        {
3428            let mut map = inner.pending_transactions.lock();
3429            map.insert(perm_tx_id, perm_tx);
3430        }
3431
3432        trace!("Sending CreatePermission to TURN server");
3433        if let Err(e) = client.send(&perm_bytes).await {
3434            debug!("CreatePermission send failed: {}", e);
3435            return Err(e);
3436        }
3437
3438        match with_timeout(inner.config.stun_timeout, perm_rx).await {
3439            Ok(Ok(msg)) => {
3440                if msg.class == StunClass::ErrorResponse {
3441                    return Err(RtcError::Internal(format!(
3442                        "CreatePermission failed: {:?}",
3443                        msg.error_code
3444                    )));
3445                }
3446
3447                // Try ChannelBind if not already bound
3448                if client.get_channel(remote.address).await.is_none()
3449                    && let Ok((bind_bytes, bind_tx_id, channel_num)) =
3450                        client.create_channel_bind_packet(remote.address).await
3451                {
3452                    let (bind_tx, bind_rx) = oneshot::channel();
3453                    {
3454                        let mut map = inner.pending_transactions.lock();
3455                        map.insert(bind_tx_id, bind_tx);
3456                    }
3457
3458                    if client.send(&bind_bytes).await.is_ok() {
3459                        let client_clone = client.clone();
3460                        let remote_addr = remote.address;
3461                        let inner_weak = Arc::downgrade(inner);
3462                        let timeout_dur = inner.config.stun_timeout;
3463
3464                        match with_timeout(timeout_dur, bind_rx).await {
3465                            Ok(Ok(msg)) => {
3466                                if msg.class == StunClass::SuccessResponse {
3467                                    client_clone.add_channel(remote_addr, channel_num).await;
3468                                }
3469                            }
3470                            _ => {
3471                                // Timeout or error: clean up pending transaction
3472                                if let Some(inner) = inner_weak.upgrade() {
3473                                    let mut map = inner.pending_transactions.lock();
3474                                    map.remove(&bind_tx_id);
3475                                }
3476                            }
3477                        }
3478                    }
3479                }
3480            }
3481            _ => {
3482                let mut map = inner.pending_transactions.lock();
3483                map.remove(&perm_tx_id);
3484                return Err(RtcError::Internal(format!("CreatePermission timeout")));
3485            }
3486        }
3487    } else if socket.is_none() && platform_socket.is_none() {
3488        return Err(RtcError::Internal(format!(
3489            "no socket found for local candidate"
3490        )));
3491    }
3492
3493    let start = Instant::now();
3494    let mut rto = Duration::from_millis(500);
3495    let max_timeout = if nominated {
3496        inner.config.nomination_timeout
3497    } else {
3498        inner.config.stun_timeout
3499    };
3500
3501    loop {
3502        if let Some(client) = &turn_client {
3503            let sent = if let Some(channel) = client.get_channel(remote.address).await {
3504                client.send_channel_data(channel, &bytes).await
3505            } else {
3506                client.send_indication(remote.address, &bytes).await
3507            };
3508
3509            if let Err(e) = sent {
3510                debug!("TURN send failed: {}", e);
3511                return Err(e);
3512            }
3513        } else if local.transport == "tcp" {
3514            // For TCP transport (active side): connect to remote and send STUN
3515            let tcp_stream = match TcpStream::connect(remote.address).await {
3516                Ok(stream) => {
3517                    stream.set_nodelay(true).ok();
3518                    stream
3519                }
3520                Err(e) => {
3521                    debug!("TCP connect to {} failed: {}", remote.address, e);
3522                    return Err(e.into());
3523                }
3524            };
3525            #[cfg(feature = "std")]
3526            use tokio::io::{AsyncReadExt, AsyncWriteExt};
3527            let mut tcp_stream = tcp_stream;
3528            let mut framed = Vec::with_capacity(2 + bytes.len());
3529            let flen = bytes.len() as u16;
3530            framed.extend_from_slice(&flen.to_be_bytes());
3531            framed.extend_from_slice(&bytes);
3532            tcp_stream.write_all(&framed).await?;
3533            // Read STUN response with TCP framing
3534            match with_timeout(inner.config.stun_timeout, async {
3535                let mut len_buf = [0u8; 2];
3536                tcp_stream.read_exact(&mut len_buf).await?;
3537                let resp_len = u16::from_be_bytes(len_buf) as usize;
3538                let mut resp_buf = vec![0u8; resp_len];
3539                tcp_stream.read_exact(&mut resp_buf).await?;
3540                StunMessage::decode(&resp_buf)
3541                    .map_err(|e| RtcError::Internal(format!("stun decode: {e}")))
3542            })
3543            .await
3544            {
3545                Ok(Ok(parsed)) => {
3546                    if parsed.class == StunClass::SuccessResponse {
3547                        return Ok(());
3548                    }
3549                    return Err(RtcError::Internal(format!(
3550                        "TCP binding check failed: unexpected response"
3551                    )));
3552                }
3553                Ok(Err(e)) => return Err(e),
3554                Err(_) => return Err(RtcError::Internal(format!("TCP binding check timeout"))),
3555            }
3556        } else if let Some(platform_socket) = &platform_socket {
3557            // Platform sockets report NetError without an errno carrier:
3558            // treat every send failure as transient and wait for the next
3559            // RTO (matches the tolerant path below).
3560            if let Err(e) = platform_socket.send_to(&bytes, remote.address).await {
3561                debug!(
3562                    "platform socket send_to {} failed (transient): {}",
3563                    remote.address, e
3564                );
3565            }
3566        } else if let Some(socket) = &socket
3567            && let Err(e) = socket.send_to(&bytes, remote.address).await
3568        {
3569            // std inspects io::ErrorKind; no_std treats everything as
3570            // transient (the Platform socket reports errors as text).
3571            let is_fatal = {
3572                #[cfg(feature = "std")]
3573                {
3574                    matches!(
3575                        e.kind(),
3576                        std::io::ErrorKind::BrokenPipe
3577                            | std::io::ErrorKind::ConnectionReset
3578                            | std::io::ErrorKind::NotConnected
3579                    )
3580                }
3581                #[cfg(not(feature = "std"))]
3582                {
3583                    false
3584                }
3585            };
3586            if is_fatal {
3587                debug!(
3588                    "socket.send_to {} fatal error, aborting nomination: {}",
3589                    remote.address, e
3590                );
3591                return Err(e.into());
3592            }
3593            // Transient error (e.g., EHOSTUNREACH / os error 65 during route setup).
3594            // Treat as a dropped send — wait for next RTO and retry.
3595        }
3596
3597        let timeout_fut = crate::platform::task::sleep(max_timeout.saturating_sub(start.elapsed()));
3598        let rto_fut = crate::platform::task::sleep(rto);
3599
3600        let mut timeout_fut = core::pin::pin!(timeout_fut);
3601        let mut rto_fut = core::pin::pin!(rto_fut);
3602        match crate::platform::select::select3(&mut rx, &mut timeout_fut, &mut rto_fut).await {
3603            crate::platform::select::Which3::A(res) => {
3604                let parsed = match res {
3605                    Ok(msg) => msg,
3606                    Err(_) => return Err(RtcError::Internal(format!("channel closed"))),
3607                };
3608
3609                if parsed.transaction_id != tx_id {
3610                    return Err(RtcError::Internal(format!(
3611                        "binding response transaction mismatch"
3612                    )));
3613                }
3614                if parsed.method != StunMethod::Binding {
3615                    return Err(RtcError::Internal(format!(
3616                        "unexpected STUN method in binding response"
3617                    )));
3618                }
3619                if parsed.class != StunClass::SuccessResponse {
3620                    return Err(RtcError::Internal(format!("binding request failed")));
3621                }
3622                return Ok(());
3623            }
3624            crate::platform::select::Which3::B(_) => {
3625                return Err(RtcError::Internal(format!("timeout")));
3626            }
3627            crate::platform::select::Which3::C(_) => {
3628                if start.elapsed() >= max_timeout {
3629                    continue;
3630                }
3631                trace!(
3632                    "Retransmitting STUN Request to {} tx={:?}",
3633                    remote.address, tx_id
3634                );
3635                rto = core::cmp::min(rto * 2, Duration::from_millis(1600));
3636            }
3637        }
3638    }
3639}
3640
3641/// Perform a STUN binding check over a TCP connection.
3642///
3643/// For TCP candidates (RFC 6544):
3644/// 1. Connect to the remote peer's TCP address
3645/// 2. Send the STUN binding request over the TCP stream
3646/// 3. Read the response, decode it, and deliver it to the pending transaction
3647/// 4. Store the stream for later media use
3648///
3649/// RFC 4571 STUN/TCP framing used by WebRTC (length prefix + message).
3650#[cfg(feature = "std")]
3651fn frame_stun_for_tcp(data: &[u8]) -> Vec<u8> {
3652    let len = data.len() as u16;
3653    let mut framed = Vec::with_capacity(2 + data.len());
3654    framed.extend_from_slice(&len.to_be_bytes());
3655    framed.extend_from_slice(data);
3656    framed
3657}
3658
3659#[cfg(feature = "std")]
3660type TcpReadHalf = tokio::net::tcp::OwnedReadHalf;
3661#[cfg(feature = "std")]
3662type TcpWriteHalf = tokio::net::tcp::OwnedWriteHalf;
3663
3664#[cfg(feature = "std")]
3665fn split_tcp_stream(stream: TcpStream, peer: SocketAddr) -> IceSocketWrapper {
3666    if let Err(e) = stream.set_nodelay(true) {
3667        debug!("TCP set_nodelay failed: {}", e);
3668    }
3669    let (read, write) = stream.into_split();
3670    #[cfg(feature = "std")]
3671    IceSocketWrapper::TcpStream(
3672        Arc::new(Mutex::new(read)),
3673        Arc::new(Mutex::new(write)),
3674        peer,
3675    )
3676}
3677
3678#[cfg(feature = "std")]
3679pub(crate) async fn attach_demuxed_tcp_stream(
3680    inner: Arc<IceTransportInner>,
3681    stream: TcpStream,
3682    peer_addr: SocketAddr,
3683    listen_addr: SocketAddr,
3684    first_packet: Vec<u8>,
3685) {
3686    let wrapper = split_tcp_stream(stream, peer_addr);
3687    inner
3688        .gatherer
3689        .store_tcp_stream(listen_addr, wrapper.clone());
3690    let _ = inner.gatherer.socket_tx.send(wrapper.clone());
3691    let mut marshal_buf = Vec::new();
3692    handle_packet(&first_packet, peer_addr, inner, wrapper, &mut marshal_buf).await;
3693}
3694
3695#[cfg(feature = "std")]
3696pub(crate) async fn tcp_write_all(write: &Arc<Mutex<TcpWriteHalf>>, data: &[u8]) -> RtcResult<()> {
3697    let mut offset = 0;
3698    while offset < data.len() {
3699        let guard = write.lock().await;
3700        loop {
3701            match guard.try_write(&data[offset..]) {
3702                Ok(0) => guard.writable().await?,
3703                Ok(n) => {
3704                    offset += n;
3705                    break;
3706                }
3707                Err(e) if e.kind() == ErrorKind::WouldBlock => guard.writable().await?,
3708                Err(e) => return Err(RtcError::Internal(format!("TCP write failed: {}", e))),
3709            }
3710        }
3711    }
3712    Ok(())
3713}
3714
3715#[cfg(feature = "std")]
3716async fn perform_tcp_binding_check(
3717    local: &IceCandidate,
3718    remote: &IceCandidate,
3719    inner: &Arc<IceTransportInner>,
3720    role: IceRole,
3721    nominated: bool,
3722) -> RtcResult<()> {
3723    debug!(
3724        "perform_tcp_binding_check: {} -> {}",
3725        local.address, remote.address
3726    );
3727    let local_params = inner.local_parameters.lock().clone();
3728    let remote_params = match inner.remote_parameters.lock().clone() {
3729        Some(p) => p,
3730        None => return Err(RtcError::Internal(format!("no remote params"))),
3731    };
3732
3733    let tx_id = random_bytes::<12>();
3734    let mut msg = StunMessage::binding_request(tx_id, Some("rustrtc"));
3735    let username = format!(
3736        "{}:{}",
3737        remote_params.username_fragment, local_params.username_fragment
3738    );
3739    msg.attributes.push(StunAttribute::Username(username));
3740    msg.attributes.push(StunAttribute::Priority(local.priority));
3741    match role {
3742        IceRole::Controlling => {
3743            msg.attributes
3744                .push(StunAttribute::IceControlling(local_params.tie_breaker));
3745            if nominated {
3746                msg.attributes.push(StunAttribute::UseCandidate);
3747            }
3748        }
3749        IceRole::Controlled => msg
3750            .attributes
3751            .push(StunAttribute::IceControlled(local_params.tie_breaker)),
3752    }
3753    let bytes = msg.encode(Some(remote_params.password.as_bytes()), true)?;
3754
3755    // Establish TCP connection to the remote peer
3756    let connect_timeout = inner.config.stun_timeout;
3757    let stream = with_timeout(connect_timeout, TcpStream::connect(remote.address))
3758        .await
3759        .map_err(|_| RtcError::Internal(format!("TCP connect timeout to {}", remote.address)))?
3760        .map_err(|e| {
3761            RtcError::Internal(format!("TCP connect to {} failed: {}", remote.address, e))
3762        })?;
3763
3764    let local_addr = stream.local_addr()?;
3765    let wrapper = split_tcp_stream(stream, remote.address);
3766    let write = match &wrapper {
3767        #[cfg(feature = "std")]
3768        IceSocketWrapper::TcpStream(_, write, _) => write.clone(),
3769        _ => return Err(RtcError::Internal(format!("split_tcp_stream invariant"))),
3770    };
3771
3772    // Register the TCP stream with the runner so its read loop handles incoming STUN responses
3773    inner.gatherer.store_tcp_stream(local_addr, wrapper.clone());
3774    let _ = inner.gatherer.socket_tx.send(wrapper);
3775
3776    // Register pending transaction
3777    let (tx, mut rx) = oneshot::channel();
3778    {
3779        let mut map = inner.pending_transactions.lock();
3780        map.insert(tx_id, tx);
3781    }
3782    let _guard = TransactionGuard {
3783        map: &inner.pending_transactions,
3784        tx_id,
3785    };
3786
3787    // Send STUN binding request over TCP (RFC 4571 framed)
3788    {
3789        let framed = frame_stun_for_tcp(&bytes);
3790        #[cfg(feature = "std")]
3791        tcp_write_all(&write, &framed).await?;
3792    }
3793
3794    // Wait for response (with retransmissions) via read loop → pending_transactions
3795    let start = Instant::now();
3796    let mut rto = Duration::from_millis(500);
3797    let max_timeout = if nominated {
3798        inner.config.nomination_timeout
3799    } else {
3800        inner.config.stun_timeout
3801    };
3802
3803    loop {
3804        let timeout_fut = crate::platform::task::sleep(max_timeout.saturating_sub(start.elapsed()));
3805        let rto_fut = crate::platform::task::sleep(rto);
3806
3807        let mut timeout_fut = core::pin::pin!(timeout_fut);
3808        let mut rto_fut = core::pin::pin!(rto_fut);
3809        match crate::platform::select::select3(&mut rx, &mut timeout_fut, &mut rto_fut).await {
3810            crate::platform::select::Which3::A(res) => {
3811                let parsed = match res {
3812                    Ok(msg) => msg,
3813                    Err(_) => return Err(RtcError::Internal(format!("channel closed"))),
3814                };
3815                if parsed.transaction_id != tx_id {
3816                    return Err(RtcError::Internal(format!(
3817                        "binding response transaction mismatch"
3818                    )));
3819                }
3820                if parsed.method != StunMethod::Binding {
3821                    return Err(RtcError::Internal(format!(
3822                        "unexpected STUN method in binding response"
3823                    )));
3824                }
3825                if parsed.class != StunClass::SuccessResponse {
3826                    return Err(RtcError::Internal(format!("binding request failed")));
3827                }
3828                return Ok(());
3829            }
3830            crate::platform::select::Which3::B(_) => {
3831                return Err(RtcError::Internal(format!("timeout")));
3832            }
3833            crate::platform::select::Which3::C(_) => {
3834                if start.elapsed() >= max_timeout {
3835                    continue;
3836                }
3837                trace!(
3838                    "TCP Retransmitting STUN Request to {} tx={:?}",
3839                    remote.address, tx_id
3840                );
3841                rto = core::cmp::min(rto * 2, Duration::from_millis(1600));
3842                let framed = frame_stun_for_tcp(&bytes);
3843                #[cfg(feature = "std")]
3844                let _ = tcp_write_all(&write, &framed).await;
3845            }
3846        }
3847    }
3848}
3849
3850/// Store `new` into the ICE state watch, notifying subscribers only when the
3851/// value actually changes.
3852///
3853/// `watch::Sender::send` marks the channel changed even for an identical value,
3854/// so re-sending `Connected` on hot paths (USE-CANDIDATE consent keepalives,
3855/// per-check-round completion) made the peer-connection state loop log
3856/// "ICE recovered" and re-broadcast on every keepalive.
3857fn store_ice_state(state: &watch::Sender<IceTransportState>, new: IceTransportState) {
3858    if *state.borrow() != new {
3859        let _ = state.send(new);
3860    }
3861}
3862
3863impl IceTransportInner {
3864    fn set_state(&self, new: IceTransportState) {
3865        store_ice_state(&self.state, new);
3866    }
3867}
3868
3869#[cfg(all(test, feature = "std"))]
3870mod state_tests {
3871    use super::{IceTransportState, store_ice_state};
3872    use tokio::sync::watch;
3873
3874    /// Regression guard: an unchanged value must not wake subscribers.
3875    #[test]
3876    fn store_ice_state_dedupes_equal_values() {
3877        let (tx, mut rx) = watch::channel(IceTransportState::Checking);
3878        store_ice_state(&tx, IceTransportState::Connected);
3879        assert!(rx.has_changed().unwrap());
3880        let _ = rx.borrow_and_update();
3881        store_ice_state(&tx, IceTransportState::Connected);
3882        assert!(
3883            !rx.has_changed().unwrap(),
3884            "re-sending an equal state must not notify (ICE recovered spam)"
3885        );
3886        store_ice_state(&tx, IceTransportState::Disconnected);
3887        assert!(rx.has_changed().unwrap());
3888    }
3889}
3890
3891#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3892pub enum IceTransportState {
3893    New,
3894    Checking,
3895    Connected,
3896    Completed,
3897    Failed,
3898    Disconnected,
3899    Closed,
3900}
3901
3902#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3903pub enum IceGathererState {
3904    New,
3905    Gathering,
3906    Complete,
3907}
3908
3909#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3910pub enum IceRole {
3911    Controlling,
3912    Controlled,
3913}
3914
3915/// TCP candidate type per RFC 6544 § 4.5.
3916#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3917pub enum TcpType {
3918    Active,
3919    Passive,
3920    So,
3921}
3922
3923impl TcpType {
3924    fn as_str(&self) -> &'static str {
3925        match self {
3926            TcpType::Active => "active",
3927            TcpType::Passive => "passive",
3928            TcpType::So => "so",
3929        }
3930    }
3931
3932    fn from_str(s: &str) -> Option<Self> {
3933        match s {
3934            "active" => Some(TcpType::Active),
3935            "passive" => Some(TcpType::Passive),
3936            "so" => Some(TcpType::So),
3937            _ => None,
3938        }
3939    }
3940}
3941
3942#[derive(Debug, Clone, PartialEq, Eq)]
3943pub struct IceCandidate {
3944    pub foundation: String,
3945    pub priority: u32,
3946    pub address: SocketAddr,
3947    pub typ: IceCandidateType,
3948    pub transport: String,
3949    pub tcp_type: Option<TcpType>,
3950    pub related_address: Option<SocketAddr>,
3951    pub component: u16,
3952    /// mDNS hostname advertised in SDP instead of `address.ip()` (RFC 6762 /
3953    /// draft-ietf-rtcweb-mdns). The real address is kept internally so
3954    /// connectivity checks and pairing work unchanged; only the SDP wire form
3955    /// is obfuscated.
3956    pub hostname: Option<String>,
3957}
3958
3959impl IceCandidate {
3960    fn compute_foundation(typ: IceCandidateType, base_addr: SocketAddr, transport: &str) -> String {
3961        // FNV-1a 64-bit: deterministic across platforms (std's DefaultHasher
3962        // is randomly seeded per process — wrong for a session-persistent
3963        // candidate foundation anyway) and available under no_std.
3964        fn fnv1a(parts: &[&[u8]]) -> u64 {
3965            let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
3966            for part in parts {
3967                for b in *part {
3968                    hash ^= u64::from(*b);
3969                    hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
3970                }
3971                hash ^= 0xff; // part separator
3972                hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
3973            }
3974            hash
3975        }
3976        let typ_label = alloc::format!("{typ:?}");
3977        let ip_label = base_addr.ip().to_string();
3978        format!(
3979            "{:x}",
3980            fnv1a(&[
3981                typ_label.as_bytes(),
3982                ip_label.as_bytes(),
3983                transport.as_bytes(),
3984            ])
3985        )
3986    }
3987
3988    /// Set the TCP type on this candidate (for peer-reflexive discovery over TCP).
3989    pub fn with_tcp_type(mut self, tcp_type: TcpType) -> Self {
3990        self.tcp_type = Some(tcp_type);
3991        self.transport = "tcp".into();
3992        self
3993    }
3994
3995    pub fn host(address: SocketAddr, component: u16) -> Self {
3996        Self {
3997            foundation: Self::compute_foundation(IceCandidateType::Host, address, "udp"),
3998            priority: IceCandidate::priority_for(IceCandidateType::Host, component),
3999            address,
4000            typ: IceCandidateType::Host,
4001            transport: "udp".into(),
4002            tcp_type: None,
4003            related_address: None,
4004            component,
4005            hostname: None,
4006        }
4007    }
4008
4009    pub fn host_tcp(address: SocketAddr, component: u16, tcp_type: TcpType) -> Self {
4010        Self {
4011            foundation: Self::compute_foundation(IceCandidateType::Host, address, "tcp"),
4012            priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
4013            address,
4014            typ: IceCandidateType::Host,
4015            transport: "tcp".into(),
4016            tcp_type: Some(tcp_type),
4017            related_address: None,
4018            component,
4019            hostname: None,
4020        }
4021    }
4022
4023    pub fn tcp(address: SocketAddr, component: u16, tcptype_str: &str) -> Self {
4024        let transport = "tcp";
4025        let tcp_type = TcpType::from_str(tcptype_str).unwrap_or(TcpType::Passive);
4026        Self {
4027            foundation: Self::compute_foundation(IceCandidateType::Host, address, transport),
4028            priority: IceCandidate::priority_for_tcp(IceCandidateType::Host, component, tcp_type),
4029            address,
4030            typ: IceCandidateType::Host,
4031            transport: transport.into(),
4032            tcp_type: Some(tcp_type),
4033            related_address: None,
4034            component,
4035            hostname: None,
4036        }
4037    }
4038
4039    /// Advertise this candidate's address in SDP as an mDNS hostname
4040    /// (draft-ietf-rtcweb-mdns). The internal address is unchanged.
4041    pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
4042        self.hostname = Some(hostname.into());
4043        self
4044    }
4045
4046    pub fn base_address(&self) -> SocketAddr {
4047        if self.typ == IceCandidateType::ServerReflexive || self.typ == IceCandidateType::Host {
4048            self.related_address.unwrap_or(self.address)
4049        } else {
4050            self.address
4051        }
4052    }
4053
4054    fn server_reflexive(base: SocketAddr, mapped: SocketAddr, component: u16) -> Self {
4055        Self {
4056            foundation: Self::compute_foundation(IceCandidateType::ServerReflexive, base, "udp"),
4057            priority: IceCandidate::priority_for(IceCandidateType::ServerReflexive, component),
4058            address: mapped,
4059            typ: IceCandidateType::ServerReflexive,
4060            transport: "udp".into(),
4061            tcp_type: None,
4062            related_address: Some(base),
4063            component,
4064            hostname: None,
4065        }
4066    }
4067
4068    fn relay(mapped: SocketAddr, component: u16, transport: &str) -> Self {
4069        Self {
4070            foundation: Self::compute_foundation(IceCandidateType::Relay, mapped, transport),
4071            priority: IceCandidate::priority_for(IceCandidateType::Relay, component),
4072            address: mapped,
4073            typ: IceCandidateType::Relay,
4074            transport: transport.into(),
4075            tcp_type: None,
4076            related_address: None,
4077            component,
4078            hostname: None,
4079        }
4080    }
4081
4082    fn priority_for(typ: IceCandidateType, component: u16) -> u32 {
4083        let type_pref = match typ {
4084            IceCandidateType::Host => 126u32,
4085            IceCandidateType::PeerReflexive => 110u32,
4086            IceCandidateType::ServerReflexive => 100u32,
4087            IceCandidateType::Relay => 0u32,
4088        };
4089        let local_pref = 65_535u32;
4090        let component = component.min(256) as u32;
4091        (type_pref << 24) | (local_pref << 8) | (256 - component)
4092    }
4093
4094    /// Priority for TCP candidates per RFC 6544 § 4.1.
4095    /// TCP candidates use a different local preference to distinguish
4096    /// between active, passive, and SO types, while UDP candidates always
4097    /// use the full 65535 local preference.
4098    fn priority_for_tcp(typ: IceCandidateType, component: u16, tcp_type: TcpType) -> u32 {
4099        let type_pref = match typ {
4100            IceCandidateType::Host => 126u32,
4101            IceCandidateType::PeerReflexive => 110u32,
4102            IceCandidateType::ServerReflexive => 100u32,
4103            IceCandidateType::Relay => 0u32,
4104        };
4105        // RFC 6544 § 4.1: local preference for TCP candidates
4106        let local_pref = match tcp_type {
4107            TcpType::Passive => 65535u32,
4108            TcpType::Active => 65534u32,
4109            TcpType::So => 65533u32,
4110        };
4111        let component = component.min(256) as u32;
4112        (type_pref << 24) | (local_pref << 8) | (256 - component)
4113    }
4114
4115    pub fn to_sdp(&self) -> String {
4116        let advertised_ip = match &self.hostname {
4117            Some(h) => h.clone(),
4118            None => self.address.ip().to_string(),
4119        };
4120        let mut parts = vec![
4121            self.foundation.clone(),
4122            self.component.to_string(),
4123            self.transport.to_ascii_lowercase(),
4124            self.priority.to_string(),
4125            advertised_ip,
4126            self.address.port().to_string(),
4127            "typ".into(),
4128            self.typ.as_str().into(),
4129        ];
4130        if let Some(tcp_type) = self.tcp_type {
4131            parts.push("tcptype".into());
4132            parts.push(tcp_type.as_str().into());
4133        }
4134        if let Some(addr) = self.related_address
4135            && self.typ != IceCandidateType::Host
4136        {
4137            parts.push("raddr".into());
4138            parts.push(addr.ip().to_string());
4139            parts.push("rport".into());
4140            parts.push(addr.port().to_string());
4141        }
4142        parts.join(" ")
4143    }
4144
4145    pub fn from_sdp(sdp: &str) -> RtcResult<Self> {
4146        let parts: Vec<&str> = sdp.split_whitespace().collect();
4147        if parts.len() < 8 {
4148            return Err(RtcError::Internal(format!("invalid candidate")));
4149        }
4150        // Handle "candidate:" prefix if present (though usually it's the attribute key)
4151        let start_idx = 0;
4152
4153        let foundation = parts[start_idx]
4154            .trim_start_matches("candidate:")
4155            .to_string();
4156        let component = parts[start_idx + 1].parse::<u16>()?;
4157        let transport = parts[start_idx + 2].to_ascii_lowercase();
4158        let priority = parts[start_idx + 3].parse::<u32>()?;
4159        let ip_str = parts[start_idx + 4];
4160        let port = parts[start_idx + 5].parse::<u16>()?;
4161        let typ_str = parts[start_idx + 7];
4162
4163        // IPv6 addresses need brackets when combined with port
4164        let address = if ip_str.contains(':') {
4165            format!("[{}]:{}", ip_str, port).parse()?
4166        } else {
4167            format!("{}:{}", ip_str, port).parse()?
4168        };
4169
4170        let typ = match typ_str {
4171            "host" => IceCandidateType::Host,
4172            "srflx" => IceCandidateType::ServerReflexive,
4173            "prflx" => IceCandidateType::PeerReflexive,
4174            "relay" => IceCandidateType::Relay,
4175            _ => return Err(RtcError::Internal(format!("unknown type"))),
4176        };
4177
4178        // Parse optional tcptype attribute (RFC 6544)
4179        let tcp_type = if transport == "tcp" {
4180            // Search for "tcptype" keyword at even indices after position 8
4181            let mut i = 8;
4182            loop {
4183                if i + 1 >= parts.len() {
4184                    break None;
4185                }
4186                match parts[i] {
4187                    "tcptype" => {
4188                        break TcpType::from_str(parts[i + 1]);
4189                    }
4190                    _ => {
4191                        i += 2;
4192                    }
4193                }
4194            }
4195        } else {
4196            None
4197        };
4198
4199        Ok(Self {
4200            foundation,
4201            priority,
4202            address,
4203            typ,
4204            transport,
4205            tcp_type,
4206            related_address: None,
4207            component,
4208            hostname: None,
4209        })
4210    }
4211}
4212
4213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4214pub enum IceCandidateType {
4215    Host,
4216    ServerReflexive,
4217    PeerReflexive,
4218    Relay,
4219}
4220
4221impl IceCandidateType {
4222    fn as_str(&self) -> &'static str {
4223        match self {
4224            IceCandidateType::Host => "host",
4225            IceCandidateType::ServerReflexive => "srflx",
4226            IceCandidateType::PeerReflexive => "prflx",
4227            IceCandidateType::Relay => "relay",
4228        }
4229    }
4230}
4231
4232#[derive(Debug, Clone, PartialEq, Eq)]
4233pub struct IceCandidatePair {
4234    pub local: IceCandidate,
4235    pub remote: IceCandidate,
4236    pub nominated: bool,
4237}
4238
4239impl IceCandidatePair {
4240    pub fn new(local: IceCandidate, remote: IceCandidate) -> Self {
4241        Self {
4242            local,
4243            remote,
4244            nominated: false,
4245        }
4246    }
4247
4248    pub fn priority(&self, role: IceRole) -> u64 {
4249        let g = self.local.priority as u64;
4250        let d = self.remote.priority as u64;
4251        let (g, d) = match role {
4252            IceRole::Controlling => (g, d),
4253            IceRole::Controlled => (d, g),
4254        };
4255        (1u64 << 32) * core::cmp::min(g, d) + 2 * core::cmp::max(g, d) + if g > d { 1 } else { 0 }
4256    }
4257}
4258
4259#[derive(Debug, Clone)]
4260pub struct IceParameters {
4261    pub username_fragment: String,
4262    pub password: String,
4263    pub ice_lite: bool,
4264    pub tie_breaker: u64,
4265}
4266
4267impl IceParameters {
4268    pub fn new(username_fragment: impl Into<String>, password: impl Into<String>) -> Self {
4269        Self {
4270            username_fragment: username_fragment.into(),
4271            password: password.into(),
4272            ice_lite: false,
4273            tie_breaker: random_u64(),
4274        }
4275    }
4276
4277    fn generate() -> Self {
4278        let ufrag = hex_encode(&random_bytes::<8>());
4279        let pwd = hex_encode(&random_bytes::<16>());
4280        Self {
4281            username_fragment: ufrag,
4282            password: pwd,
4283            ice_lite: false,
4284            tie_breaker: random_u64(),
4285        }
4286    }
4287}
4288
4289#[derive(Debug, Clone)]
4290pub struct IceTransportBuilder {
4291    config: RtcConfiguration,
4292    role: IceRole,
4293    servers: Vec<IceServer>,
4294}
4295
4296impl IceTransportBuilder {
4297    pub fn new(config: RtcConfiguration) -> Self {
4298        Self {
4299            config,
4300            role: IceRole::Controlled,
4301            servers: Vec::new(),
4302        }
4303    }
4304
4305    pub fn role(mut self, role: IceRole) -> Self {
4306        self.role = role;
4307        self
4308    }
4309
4310    pub fn server(mut self, server: IceServer) -> Self {
4311        self.servers.push(server);
4312        self
4313    }
4314
4315    pub fn build(self) -> (IceTransport, impl core::future::Future<Output = ()> + Send) {
4316        let mut config = self.config.clone();
4317        config.ice_servers.extend(self.servers);
4318        let (transport, runner) = IceTransport::new(config);
4319        transport.set_role(self.role);
4320        if let Err(err) = transport.start_gathering() {
4321            debug!("ICE gather failed: {}", err);
4322        }
4323        (transport, runner)
4324    }
4325}
4326
4327#[derive(Debug, Clone)]
4328struct IceGatherer {
4329    state: Arc<crate::platform::sync::Mutex<IceGathererState>>,
4330    local_candidates: Arc<crate::platform::sync::Mutex<Vec<IceCandidate>>>,
4331    sockets: Arc<crate::platform::sync::Mutex<Vec<Arc<UdpSocket>>>>,
4332    /// Sockets created through the platform bind factory
4333    /// ([`crate::platform::net::set_udp_bind_fn`] — rtcembed's embassy-net
4334    /// backend, or a host-test loopback). Kept so socket resolution can
4335    /// send from the same base socket the candidate advertises.
4336    platform_sockets: Arc<
4337        crate::platform::sync::Mutex<Vec<Arc<dyn crate::platform::net::UdpSocket>>>,
4338    >,
4339    #[cfg_attr(not(feature = "std"), allow(dead_code))]
4340    tcp_listeners: Arc<crate::platform::sync::Mutex<Vec<Arc<TcpListener>>>>,
4341    tcp_streams: Arc<crate::platform::sync::Mutex<BTreeMap<SocketAddr, IceSocketWrapper>>>,
4342    #[cfg(feature = "std")]
4343    shared_tcp_regs: Arc<crate::platform::sync::Mutex<Vec<shared_tcp::SharedTcpRegistration>>>,
4344    #[cfg(not(feature = "std"))]
4345    shared_tcp_regs: Arc<crate::platform::sync::Mutex<Vec<SharedTcpRegistration>>>,
4346    shared_udp_regs: Arc<crate::platform::sync::Mutex<Vec<shared_udp::SharedUdpRegistration>>>,
4347    /// The shared UDP mux socket wrapper (when `ice_udp_mux` is enabled).
4348    /// Stored so `resolve_socket` can return it for sending.
4349    shared_udp_socket: Arc<crate::platform::sync::Mutex<Option<IceSocketWrapper>>>,
4350    transport_inner:
4351        Arc<crate::platform::sync::Mutex<Option<alloc::sync::Weak<IceTransportInner>>>>,
4352    turn_clients: Arc<crate::platform::sync::Mutex<BTreeMap<SocketAddr, Arc<TurnClient>>>>,
4353    upnp_mappers: Arc<crate::platform::sync::Mutex<Vec<UpnpPortMapper>>>,
4354    config: RtcConfiguration,
4355    candidate_tx: broadcast::Sender<IceCandidate>,
4356    socket_tx: crate::platform::sync::mpsc::UnboundedSender<IceSocketWrapper>,
4357}
4358
4359impl IceGatherer {
4360    fn new(
4361        config: RtcConfiguration,
4362        candidate_tx: broadcast::Sender<IceCandidate>,
4363        socket_tx: crate::platform::sync::mpsc::UnboundedSender<IceSocketWrapper>,
4364    ) -> Self {
4365        Self {
4366            state: Arc::new(crate::platform::sync::Mutex::new(IceGathererState::New)),
4367            local_candidates: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4368            sockets: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4369            platform_sockets: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4370            tcp_listeners: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4371            tcp_streams: Arc::new(crate::platform::sync::Mutex::new(BTreeMap::new())),
4372            shared_tcp_regs: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4373            shared_udp_regs: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4374            shared_udp_socket: Arc::new(crate::platform::sync::Mutex::new(None)),
4375            transport_inner: Arc::new(crate::platform::sync::Mutex::new(None)),
4376            turn_clients: Arc::new(crate::platform::sync::Mutex::new(BTreeMap::new())),
4377            upnp_mappers: Arc::new(crate::platform::sync::Mutex::new(Vec::new())),
4378            config,
4379            candidate_tx,
4380            socket_tx,
4381        }
4382    }
4383
4384    fn set_transport(&self, inner: alloc::sync::Weak<IceTransportInner>) {
4385        *self.transport_inner.lock() = Some(inner);
4386    }
4387
4388    /// The external IP to advertise as a srflx candidate (ServerReflexive
4389    /// mode, WebRTC, non-loopback bind, non-relay policy).
4390    fn external_srflx_ip(&self, bind_ip: IpAddr) -> Option<IpAddr> {
4391        if self.config.external_ip_candidate_type
4392            != crate::config::ExternalIpCandidateType::ServerReflexive
4393            || self.config.transport_mode != crate::config::TransportMode::WebRtc
4394            || self.config.ice_transport_policy != IceTransportPolicy::All
4395            || bind_ip.is_loopback()
4396        {
4397            return None;
4398        }
4399        self.config.external_ip.as_ref()?.parse().ok()
4400    }
4401
4402    /// Push the host candidate for a socket bound at `local_addr`, plus the
4403    /// external IP as a server-reflexive candidate on the same socket.
4404    /// Returns false when the mode does not apply.
4405    fn push_host_with_external_srflx(
4406        &self,
4407        local_addr: SocketAddr,
4408        bind_ip: IpAddr,
4409        tcp_type: Option<TcpType>,
4410    ) -> bool {
4411        let Some(external) = self.external_srflx_ip(bind_ip) else {
4412            return false;
4413        };
4414        let mut host_addr = local_addr;
4415        if bind_ip.is_unspecified()
4416            && let Ok(local_ip) = get_local_ip()
4417        {
4418            host_addr.set_ip(local_ip);
4419        }
4420        let mut host = match tcp_type {
4421            Some(tcp_type) => IceCandidate::host_tcp(host_addr, 1, tcp_type),
4422            None => IceCandidate::host(host_addr, 1),
4423        };
4424        if host_addr != local_addr {
4425            host.related_address = Some(local_addr);
4426        }
4427        // Base = the socket's own address, as for STUN-learned candidates.
4428        let mut srflx = IceCandidate::server_reflexive(
4429            local_addr,
4430            SocketAddr::new(external, local_addr.port()),
4431            1,
4432        );
4433        if let Some(tcp_type) = tcp_type {
4434            srflx = srflx.with_tcp_type(tcp_type);
4435            srflx.foundation = IceCandidate::compute_foundation(
4436                IceCandidateType::ServerReflexive,
4437                local_addr,
4438                "tcp",
4439            );
4440            srflx.priority =
4441                IceCandidate::priority_for_tcp(IceCandidateType::ServerReflexive, 1, tcp_type);
4442        }
4443        self.push_candidate(host);
4444        if external.is_ipv4() == local_addr.is_ipv4() {
4445            self.push_candidate(srflx);
4446        }
4447        true
4448    }
4449
4450    fn push_tcp_passive_candidate(&self, local_addr: SocketAddr, bind_ip: IpAddr) {
4451        if self.push_host_with_external_srflx(local_addr, bind_ip, Some(TcpType::Passive)) {
4452            return;
4453        }
4454        if let Some(ext_ip) = &self.config.external_ip
4455            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
4456        {
4457            if !bind_ip.is_loopback() {
4458                let mut ext_addr = local_addr;
4459                ext_addr.set_ip(parsed_ip);
4460                let mut cand = IceCandidate::tcp(ext_addr, 1, "passive");
4461                cand.related_address = Some(local_addr);
4462                self.push_candidate(cand);
4463            } else {
4464                self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
4465            }
4466        } else if bind_ip.is_unspecified() {
4467            let mut cand_addr = local_addr;
4468            if let Ok(local_ip) = get_local_ip() {
4469                cand_addr.set_ip(local_ip);
4470            }
4471            let mut cand = IceCandidate::tcp(cand_addr, 1, "passive");
4472            cand.related_address = Some(local_addr);
4473            self.push_candidate(cand);
4474        } else {
4475            self.push_candidate(IceCandidate::tcp(local_addr, 1, "passive"));
4476        }
4477    }
4478
4479    /// Get the UPnP mappers for manual cleanup
4480    #[allow(dead_code)]
4481    #[cfg(feature = "std")]
4482    pub fn upnp_mappers(&self) -> Arc<crate::platform::sync::Mutex<Vec<UpnpPortMapper>>> {
4483        self.upnp_mappers.clone()
4484    }
4485
4486    /// Clean up all UPnP port mappings
4487    #[allow(dead_code)]
4488    pub async fn cleanup_upnp_mappings(&self) {
4489        let mappers = self.upnp_mappers.lock().clone();
4490        for mapper in mappers {
4491            if let Err(e) = mapper.cleanup().await {
4492                trace!("Failed to clean up UPnP mappings: {}", e);
4493            }
4494        }
4495        self.upnp_mappers.lock().clear();
4496    }
4497
4498    /// Refresh all stale UPnP port mappings (best-effort).
4499    ///
4500    /// Called periodically by the ICE runner so long-lived sessions keep their
4501    /// router leases alive. Each mapper renews its own stale mappings and a
4502    /// single failure does not abort the rest.
4503    #[cfg(feature = "std")]
4504    pub async fn renew_upnp_mappings(&self) {
4505        let mappers = self.upnp_mappers.lock().clone();
4506        for mapper in mappers {
4507            if let Err(e) = mapper.renew_all_stale().await {
4508                warn!("Failed to refresh UPnP mappings: {}", e);
4509            }
4510        }
4511    }
4512
4513    fn state(&self) -> IceGathererState {
4514        *self.state.lock()
4515    }
4516
4517    fn local_candidates(&self) -> Vec<IceCandidate> {
4518        self.local_candidates.lock().clone()
4519    }
4520
4521    async fn bind_socket(&self, ip: IpAddr) -> RtcResult<IceSocketWrapper> {
4522        if let (Some(start), Some(end)) = (self.config.rtp_start_port, self.config.rtp_end_port) {
4523            let start = start.saturating_add(start % 2);
4524            let end = end - (end % 2);
4525
4526            if start > end {
4527                return Err(RtcError::Internal(format!(
4528                    "No usable even RTP ports in range {}..={}",
4529                    start, end
4530                )));
4531            }
4532
4533            let port_count = (((end - start) / 2) + 1) as u64;
4534            let start_index = (random_u64() % port_count) as u16;
4535            let mut port = start + (start_index * 2);
4536
4537            for _ in 0..port_count {
4538                match self.bind_one(SocketAddr::new(ip, port)).await {
4539                    Ok(socket) => return Ok(socket),
4540                    Err(e) => {
4541                        // Only a genuinely busy port is worth retrying with
4542                        // the next port in the range (std io::Error and the
4543                        // platform factory both surface it as
4544                        // RtcError::AddrInUse). Any other error kind (bind IP
4545                        // not assigned to a local interface, permissions,
4546                        // ...) fails for every port in the range and must not
4547                        // be misreported as port exhaustion below.
4548                        if !e.is_addr_in_use() {
4549                            error!(
4550                                label = self.config.label.as_deref().unwrap_or("-"),
4551                                "binding RTP port {} on {} failed: {}", port, ip, e
4552                            );
4553                            return Err(e);
4554                        }
4555                        port = port.saturating_add(2);
4556                        if port > end {
4557                            port = start;
4558                        }
4559                    }
4560                }
4561            }
4562            return Err(RtcError::Internal(format!(
4563                "No available even RTP ports in range {}..={} (label={label})",
4564                start,
4565                end,
4566                label = self.config.label.as_deref().unwrap_or("-")
4567            )));
4568        } else {
4569            self.bind_one(SocketAddr::new(ip, 0)).await
4570        }
4571    }
4572
4573    /// One bind attempt through the active backend: the platform factory
4574    /// when installed ([`crate::platform::net::set_udp_bind_fn`] — rtcembed's
4575    /// embassy-net backend, or a host-test loopback), else the tokio socket
4576    /// (std). Backend bookkeeping (sockets list) is done here so callers
4577    /// only deal with the wrapper.
4578    async fn bind_one(&self, addr: SocketAddr) -> RtcResult<IceSocketWrapper> {
4579        if let Some(result) = crate::platform::net::udp_bind_via_factory(addr) {
4580            let socket = result?;
4581            let wrapper = IceSocketWrapper::Platform(socket.clone());
4582            self.platform_sockets.lock().push(socket);
4583            return Ok(wrapper);
4584        }
4585
4586        #[cfg(feature = "std")]
4587        {
4588            let socket = UdpSocket::bind(addr).await?;
4589            let socket = Arc::new(socket);
4590            self.sockets.lock().push(socket.clone());
4591            Ok(IceSocketWrapper::Udp(socket))
4592        }
4593        #[cfg(not(feature = "std"))]
4594        {
4595            let _ = addr;
4596            Err(RtcError::Internal(alloc::string::String::from(
4597                "no UDP bind backend: install one via platform::net::set_udp_bind_fn (rtcembed)",
4598            )))
4599        }
4600    }
4601
4602    /// Looks up a factory-bound platform socket by local address (exact
4603    /// match, or unspecified-IP with matching port).
4604    pub(crate) fn get_platform_socket(
4605        &self,
4606        addr: SocketAddr,
4607    ) -> Option<Arc<dyn crate::platform::net::UdpSocket>> {
4608        let sockets = self.platform_sockets.lock();
4609        sockets.iter().find_map(|socket| {
4610            let local = socket.local_addr().ok()?;
4611            let matches =
4612                local == addr || (local.ip().is_unspecified() && local.port() == addr.port());
4613            matches.then(|| socket.clone())
4614        })
4615    }
4616
4617    /// Registers a bound socket: backend bookkeeping (tokio sockets list;
4618    /// platform sockets are registered in `bind_one`) plus handing the
4619    /// wrapper to the runner so it starts a read loop.
4620    pub(crate) fn register_bound(&self, wrapper: &IceSocketWrapper) {
4621        if let IceSocketWrapper::Udp(s) = wrapper {
4622            self.sockets.lock().push(s.clone());
4623        }
4624        let _ = self.socket_tx.send(wrapper.clone());
4625    }
4626
4627    fn get_socket(&self, addr: SocketAddr) -> Option<Arc<UdpSocket>> {
4628        let found = {
4629            let sockets = self.sockets.lock();
4630            sockets.iter().find_map(|socket| {
4631                let local = socket.local_addr().ok()?;
4632                let matches =
4633                    local == addr || (local.ip().is_unspecified() && local.port() == addr.port());
4634                matches.then(|| socket.clone())
4635            })
4636        };
4637        if let Some(s) = found {
4638            return Some(s);
4639        }
4640        // Fallback: the shared UDP mux socket (sending side). It backs the single
4641        // host candidate when `ice_udp_mux` is enabled and is not stored in
4642        // `sockets` (to keep a single demux read loop).
4643        if let Some(IceSocketWrapper::SharedUdp(handle)) = self.shared_udp_socket.lock().clone()
4644            && let Ok(local) = handle.local_addr()
4645            && (local == addr || (local.ip().is_unspecified() && local.port() == addr.port()))
4646        {
4647            return None; // mux socket is not a tokio-UdpSocket (Platform path owns it)
4648        }
4649        // Avoid unwrap in logging to prevent panic hiding
4650        let available: Vec<String> = self
4651            .sockets
4652            .lock()
4653            .iter()
4654            .map(|s| {
4655                s.local_addr()
4656                    .map(|a| a.to_string())
4657                    .unwrap_or_else(|_| "error".to_string())
4658            })
4659            .collect();
4660        trace!(
4661            "get_socket: no socket found for {}, available: {:?}",
4662            addr, available
4663        );
4664        None
4665    }
4666
4667    fn get_tcp_socket(&self, addr: SocketAddr) -> Option<IceSocketWrapper> {
4668        let streams = self.tcp_streams.lock();
4669        for (local_addr, wrapper) in streams.iter() {
4670            if *local_addr == addr {
4671                return Some(wrapper.clone());
4672            }
4673            // Match on port only if IP is unspecified (0.0.0.0)
4674            if local_addr.ip().is_unspecified() && local_addr.port() == addr.port() {
4675                return Some(wrapper.clone());
4676            }
4677        }
4678        trace!(
4679            "get_tcp_socket: no TCP stream found for {}, available: {:?}",
4680            addr,
4681            streams.keys().collect::<Vec<_>>()
4682        );
4683        None
4684    }
4685
4686    fn store_tcp_stream(&self, local_addr: SocketAddr, wrapper: IceSocketWrapper) {
4687        self.tcp_streams.lock().insert(local_addr, wrapper);
4688    }
4689
4690    #[instrument(skip(self))]
4691    async fn gather(&self) -> RtcResult<()> {
4692        {
4693            let mut state = self.state.lock();
4694            if *state == IceGathererState::Complete {
4695                return Ok(());
4696            }
4697            *state = IceGathererState::Gathering;
4698        }
4699
4700        // Host gathering must complete first (creates sockets)
4701        let host_fut = async {
4702            if self.config.ice_transport_policy == IceTransportPolicy::All {
4703                if self.config.ice_gather_udp_hosts {
4704                    if let Err(e) = self.gather_host_candidates().await {
4705                        debug!("Host gathering failed: {}", e);
4706                    }
4707                } else if self.config.ice_tcp_policy == crate::config::IceTcpPolicy::Enabled {
4708                    // Outbound controlling peers with no TCP listen range advertise active locals.
4709                    // WHEP/answerer setups configure tcp_port_range_* for passive listeners;
4710                    // skip active placeholders so SDP does not contain invalid port 0 candidates.
4711                    let has_tcp_listen_range = match (
4712                        self.config.tcp_port_range_start,
4713                        self.config.tcp_port_range_end,
4714                    ) {
4715                        (Some(s), Some(e)) => s > 0 && e > 0 && s <= e,
4716                        _ => false,
4717                    };
4718                    if !has_tcp_listen_range
4719                        && let Err(e) = self.gather_tcp_active_candidates().await
4720                    {
4721                        debug!("TCP active gathering failed: {}", e);
4722                    }
4723                }
4724            }
4725        };
4726
4727        host_fut.await;
4728
4729        // TCP host candidate gathering
4730        if (self.config.tcp_port_range_start.is_some() || self.config.tcp_port_range_end.is_some())
4731            && let Err(e) = {
4732                #[cfg(feature = "std")]
4733                {
4734                    self.gather_tcp_host_candidates().await
4735                }
4736                #[cfg(not(feature = "std"))]
4737                {
4738                    Ok::<(), RtcError>(())
4739                }
4740            }
4741        {
4742            debug!("TCP host gathering failed: {}", e);
4743        }
4744
4745        // STUN must complete before UPnP so we can detect double-NAT
4746        // and use STUN's public IP for UPnP candidates. Bounded so a
4747        // hung STUN/TURN server (no DNS timeout, no TCP connect timeout)
4748        // can never stall the whole gather — UPnP still runs and the
4749        // gather completes with whatever candidates arrived in time.
4750        let stun_public_ip = if self.config.enable_upnp {
4751            with_timeout(
4752                Duration::from_secs(5),
4753                self.gather_servers_and_get_public_ip(),
4754            )
4755            .await
4756            .unwrap_or_else(|_| {
4757                debug!(
4758                    "STUN/TURN gathering timed out after 5s, skipping UPnP double-NAT detection"
4759                );
4760                None
4761            })
4762        } else {
4763            if let Err(e) = self.gather_servers().await {
4764                debug!("Server gathering failed: {}", e);
4765            }
4766            None
4767        };
4768
4769        // UPnP depends on host sockets and optionally STUN's public IP
4770        if self.config.enable_upnp
4771            && self.config.ice_transport_policy == IceTransportPolicy::All
4772            && let Err(e) = self.gather_upnp_candidates(stun_public_ip).await
4773        {
4774            debug!("UPnP gathering failed: {}", e);
4775        }
4776
4777        *self.state.lock() = IceGathererState::Complete;
4778        Ok(())
4779    }
4780
4781    /// Gather a host candidate backed by the process-wide shared UDP socket
4782    /// (single-port multiplexing). All PeerConnections sharing the same
4783    /// `ice_udp_mux_port` register their ufrag on the same socket; incoming
4784    /// packets are demuxed by ufrag / source address in `shared_udp`.
4785    async fn gather_shared_udp_host_candidate(&self) -> RtcResult<()> {
4786        let port = self.config.ice_udp_mux_port.ok_or_else(|| {
4787            RtcError::Internal(format!(
4788                "ice_udp_mux is enabled but ice_udp_mux_port is not set"
4789            ))
4790        })?;
4791
4792        let bind_ip = if let Some(bind_ip_str) = &self.config.bind_ip {
4793            bind_ip_str
4794                .parse::<IpAddr>()
4795                .map_err(|e| RtcError::Internal(format!("invalid bind_ip {bind_ip_str}: {e}")))?
4796        } else {
4797            // Bind on the wildcard so the shared socket accepts on every
4798            // interface; the advertised candidate IP is rewritten below.
4799            IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED)
4800        };
4801
4802        if self.config.disable_ipv6 && bind_ip.is_ipv6() {
4803            return Err(RtcError::Internal(format!(
4804                "disable_ipv6 is set but bind_ip is IPv6"
4805            )));
4806        }
4807
4808        let bind_addr = SocketAddr::new(bind_ip, port);
4809
4810        let inner = self
4811            .transport_inner
4812            .lock()
4813            .as_ref()
4814            .and_then(|weak| weak.upgrade())
4815            .ok_or_else(|| {
4816                RtcError::Internal("ICE transport unavailable during shared UDP gather".into())
4817            })?;
4818        let ufrag = inner.local_parameters.lock().username_fragment.clone();
4819
4820        let (local_addr, handle, registration) = shared_udp::acquire(bind_addr, ufrag)
4821            .await
4822            .map_err(|e| RtcError::Internal(format!("shared udp acquire: {e}")))?;
4823
4824        self.shared_udp_regs.lock().push(registration);
4825
4826        let wrapper = IceSocketWrapper::SharedUdp(Arc::new(handle));
4827        *self.shared_udp_socket.lock() = Some(wrapper.clone());
4828        let _ = self.socket_tx.send(wrapper);
4829
4830        if self.push_host_with_external_srflx(local_addr, bind_ip, None) {
4831            return Ok(());
4832        }
4833        // Derive the advertised candidate address (mirror the per-IP path).
4834        let mut cand_addr = local_addr;
4835        if let Some(ext_ip) = &self.config.external_ip
4836            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
4837        {
4838            if !bind_ip.is_loopback() {
4839                cand_addr.set_ip(parsed_ip);
4840            }
4841        } else if bind_ip.is_unspecified()
4842            && let Ok(local_ip) = get_local_ip()
4843        {
4844            cand_addr.set_ip(local_ip);
4845        }
4846
4847        let mut cand = IceCandidate::host(cand_addr, 1);
4848        if cand_addr != local_addr {
4849            cand.related_address = Some(local_addr);
4850        }
4851        self.push_candidate(cand);
4852        Ok(())
4853    }
4854
4855    async fn gather_host_candidates(&self) -> RtcResult<()> {
4856        let mut bind_ips = Vec::new();
4857
4858        if let Some(bind_ip_str) = &self.config.bind_ip {
4859            if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
4860                bind_ips.push(ip);
4861            }
4862        } else if self.config.transport_mode != crate::TransportMode::WebRtc {
4863            // Non-WebRTC mode: prefer a LAN IP if available.
4864            // Binding to 0.0.0.0 on macOS can lead to "No route to host" (os error 65)
4865            // if the destination is on a LAN segment but the OS picks a wrong default interface.
4866            if let Ok(ip) = get_local_ip() {
4867                bind_ips.push(ip);
4868            } else {
4869                bind_ips.push(IpAddr::V4(core::net::Ipv4Addr::UNSPECIFIED));
4870            }
4871        } else {
4872            // Default: bind to all LAN IPs. Loopback is only added on explicit
4873            // opt-in (`ice_include_loopback_candidates`) — advertising 127.0.0.1
4874            // to remote peers is useless and wastes their permissions/checks.
4875            if self.config.ice_include_loopback_candidates {
4876                bind_ips.push(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST));
4877            }
4878
4879            #[cfg(feature = "std")]
4880            use local_ip_address::list_afinet_netifas;
4881            #[cfg(feature = "std")]
4882            if let Ok(interfaces) = list_afinet_netifas() {
4883                for (name, addr) in interfaces {
4884                    if let IpAddr::V4(ip) = addr
4885                        && !ip.is_loopback()
4886                        && !bind_ips.contains(&IpAddr::V4(ip))
4887                    {
4888                        // Skip common virtual interface prefixes
4889                        if name.starts_with("utun")
4890                            || name.starts_with("gif")
4891                            || name.starts_with("stf")
4892                            || name.starts_with("awdl")
4893                            || name.starts_with("llw")
4894                        {
4895                            continue;
4896                        }
4897                        bind_ips.push(IpAddr::V4(ip));
4898                    }
4899                }
4900            }
4901
4902            // no_std (embedded) has no interface enumeration, so use the
4903            // embedder's local-IP seam (`platform::net`/`set_local_ip_fn`) to
4904            // produce a host candidate — otherwise the WebRTC offer carries no
4905            // candidates at all.
4906            #[cfg(not(feature = "std"))]
4907            if let Ok(ip) = get_local_ip() {
4908                bind_ips.push(ip);
4909            }
4910        }
4911
4912        if self.config.ice_udp_mux
4913            && let Err(e) = self.gather_shared_udp_host_candidate().await
4914        {
4915            debug!("Shared UDP mux host candidate failed: {}", e);
4916        }
4917
4918        for ip in &bind_ips {
4919            let ip = *ip;
4920            // When UDP mux is enabled, the shared socket already provides the
4921            // host candidate; skip per-IP UDP socket binding.
4922            if self.config.ice_udp_mux {
4923                continue;
4924            }
4925            match self.bind_socket(ip).await {
4926                Ok(socket) => {
4927                    if let Ok(addr) = socket.local_addr() {
4928                        self.register_bound(&socket);
4929
4930                        if self.push_host_with_external_srflx(addr, ip, None) {
4931                            // host + external server-reflexive pushed
4932                        } else if let Some(ext_ip) = &self.config.external_ip
4933                            && let Ok(parsed_ip) = ext_ip.parse::<IpAddr>()
4934                        {
4935                            if !ip.is_loopback() {
4936                                let mut ext_addr = addr;
4937                                ext_addr.set_ip(parsed_ip);
4938                                let mut cand = IceCandidate::host(ext_addr, 1);
4939                                cand.related_address = Some(addr);
4940                                self.push_candidate(cand);
4941                            } else {
4942                                self.push_candidate(IceCandidate::host(addr, 1));
4943                            }
4944                        } else if ip.is_unspecified() {
4945                            // If bound to 0.0.0.0 and no external_ip, try to find a reachable local IP for the candidate
4946                            let mut cand_addr = addr;
4947                            if let Ok(local_ip) = get_local_ip() {
4948                                cand_addr.set_ip(local_ip);
4949                            }
4950                            let mut cand = IceCandidate::host(cand_addr, 1);
4951                            cand.related_address = Some(addr);
4952                            self.push_candidate(cand);
4953                        } else {
4954                            self.push_candidate(IceCandidate::host(addr, 1));
4955                        }
4956                    }
4957                }
4958                Err(e) => {
4959                    if self.config.bind_ip.is_some() {
4960                        debug!(
4961                            label = self.config.label.as_deref().unwrap_or("-"),
4962                            "Failed to bind to requested bind_ip {}: {}", ip, e
4963                        );
4964                    } else if !ip.is_loopback() && !ip.is_unspecified() {
4965                        debug!(
4966                            label = self.config.label.as_deref().unwrap_or("-"),
4967                            "Failed to bind socket on {}: {}", ip, e
4968                        );
4969                    }
4970                }
4971            }
4972        }
4973
4974        // Gather TCP host candidates if TCP is enabled (std-only:
4975        // ICE-TCP is excluded from the embedded target)
4976        #[cfg(feature = "std")]
4977        if self.config.ice_tcp_policy != crate::config::IceTcpPolicy::Disabled {
4978            for ip in &bind_ips {
4979                let ip = *ip;
4980                match TcpListener::bind(SocketAddr::new(ip, 0)).await {
4981                    Ok(listener) => {
4982                        if let Ok(addr) = listener.local_addr() {
4983                            let listener = Arc::new(listener);
4984                            self.tcp_listeners.lock().push(listener.clone());
4985                            #[cfg(feature = "std")]
4986                            let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
4987
4988                            let tcp_type = TcpType::Passive;
4989                            if self.push_host_with_external_srflx(addr, ip, Some(tcp_type)) {
4990                                continue;
4991                            }
4992                            let mut cand = IceCandidate::host_tcp(addr, 1, tcp_type);
4993                            if ip.is_unspecified()
4994                                && let Ok(local_ip) = get_local_ip()
4995                            {
4996                                let mut cand_addr = addr;
4997                                cand_addr.set_ip(local_ip);
4998                                let mut ext_cand = IceCandidate::host_tcp(cand_addr, 1, tcp_type);
4999                                ext_cand.related_address = Some(addr);
5000                                cand = ext_cand;
5001                            }
5002                            self.push_candidate(cand);
5003                        }
5004                    }
5005                    Err(e) => {
5006                        debug!("Failed to bind TCP listener on {}: {}", ip, e);
5007                    }
5008                }
5009            }
5010        }
5011
5012        Ok(())
5013    }
5014
5015    /// Advertise ICE-TCP active host candidates for controlling clients (no UDP gather).
5016    ///
5017    /// RFC 6544 uses port 9 in SDP for active candidates (not 0 — browsers reject port 0).
5018    async fn gather_tcp_active_candidates(&self) -> RtcResult<()> {
5019        use core::net::{IpAddr, Ipv4Addr};
5020
5021        const ACTIVE_PLACEHOLDER_PORT: u16 = 9;
5022
5023        let mut bind_ips = Vec::new();
5024        if self.config.ice_include_loopback_candidates {
5025            bind_ips.push(IpAddr::V4(Ipv4Addr::LOCALHOST));
5026        }
5027        if let Ok(local_ip) = get_local_ip()
5028            && !bind_ips.contains(&local_ip)
5029        {
5030            bind_ips.push(local_ip);
5031        }
5032
5033        for ip in bind_ips {
5034            self.push_candidate(IceCandidate::tcp(
5035                SocketAddr::new(ip, ACTIVE_PLACEHOLDER_PORT),
5036                1,
5037                "active",
5038            ));
5039        }
5040        self.push_candidate(IceCandidate::tcp(
5041            SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), ACTIVE_PLACEHOLDER_PORT),
5042            1,
5043            "active",
5044        ));
5045        Ok(())
5046    }
5047
5048    #[cfg(feature = "std")]
5049    async fn gather_tcp_host_candidates(&self) -> RtcResult<()> {
5050        let start = self.config.tcp_port_range_start.unwrap_or(0);
5051        let end = self.config.tcp_port_range_end.unwrap_or(0);
5052
5053        if start == 0 || end == 0 || start > end {
5054            return Ok(());
5055        }
5056
5057        let bind_ips = if let Some(bind_ip_str) = &self.config.bind_ip {
5058            if let Ok(ip) = bind_ip_str.parse::<IpAddr>() {
5059                vec![ip]
5060            } else {
5061                return Ok(());
5062            }
5063        } else {
5064            let mut ips = Vec::new();
5065            if self.config.ice_include_loopback_candidates {
5066                ips.push(IpAddr::V4(core::net::Ipv4Addr::LOCALHOST));
5067            }
5068            #[cfg(feature = "std")]
5069            use local_ip_address::list_afinet_netifas;
5070            #[cfg(feature = "std")]
5071            if let Ok(interfaces) = list_afinet_netifas() {
5072                for (name, addr) in interfaces {
5073                    if let IpAddr::V4(ip) = addr
5074                        && !ip.is_loopback()
5075                        && !ips.contains(&IpAddr::V4(ip))
5076                    {
5077                        if name.starts_with("utun")
5078                            || name.starts_with("gif")
5079                            || name.starts_with("stf")
5080                            || name.starts_with("awdl")
5081                            || name.starts_with("llw")
5082                        {
5083                            continue;
5084                        }
5085                        ips.push(IpAddr::V4(ip));
5086                    }
5087                }
5088            }
5089            ips
5090        };
5091
5092        // One passive TCP listener per local IP (first free port in range). Binding the
5093        // entire range per PeerConnection exhausts the pool after a single session.
5094        // When start == end, share one listener process-wide and demux by ICE ufrag.
5095        let use_shared_listener = start == end;
5096
5097        for ip in bind_ips {
5098            if use_shared_listener {
5099                let addr = SocketAddr::new(ip, start);
5100                let inner = self
5101                    .transport_inner
5102                    .lock()
5103                    .as_ref()
5104                    .and_then(|weak| weak.upgrade())
5105                    .ok_or_else(|| {
5106                        RtcError::Internal("ICE transport unavailable during TCP gather".into())
5107                    })?;
5108                let ufrag = inner.local_parameters.lock().username_fragment.clone();
5109                match shared_tcp::acquire(addr, ufrag, Arc::downgrade(&inner)).await {
5110                    Ok((local_addr, registration)) => {
5111                        self.shared_tcp_regs.lock().push(registration);
5112                        self.push_tcp_passive_candidate(local_addr, ip);
5113                        break;
5114                    }
5115                    Err(e) => {
5116                        debug!("shared TCP listener acquire on {} failed: {}", addr, e);
5117                    }
5118                }
5119                continue;
5120            }
5121
5122            for port in start..=end {
5123                let addr = SocketAddr::new(ip, port);
5124                match TcpListener::bind(addr).await {
5125                    Ok(listener) => {
5126                        let local_addr = match listener.local_addr() {
5127                            Ok(a) => a,
5128                            Err(_) => continue,
5129                        };
5130                        let listener = Arc::new(listener);
5131                        self.tcp_listeners.lock().push(listener.clone());
5132                        #[cfg(feature = "std")]
5133                        let _ = self.socket_tx.send(IceSocketWrapper::TcpListener(listener));
5134
5135                        self.push_tcp_passive_candidate(local_addr, ip);
5136                        break;
5137                    }
5138                    Err(_) => continue,
5139                }
5140            }
5141        }
5142
5143        Ok(())
5144    }
5145
5146    async fn gather_upnp_candidates(&self, stun_public_ip: Option<IpAddr>) -> RtcResult<()> {
5147        let sockets = self.sockets.lock().clone();
5148        let timeout = self.config.upnp_discovery_timeout;
5149        let mut tasks = FuturesUnordered::new();
5150
5151        for socket in sockets {
5152            let local_addr = match socket.local_addr() {
5153                Ok(addr) => addr,
5154                Err(_) => continue,
5155            };
5156
5157            // Skip loopback addresses
5158            if local_addr.ip().is_loopback() {
5159                continue;
5160            }
5161
5162            // Skip IPv6 (UPnP IGD doesn't support IPv6 well)
5163            if local_addr.is_ipv6() {
5164                continue;
5165            }
5166
5167            let this = self.clone();
5168            let stun_ip = stun_public_ip;
5169            tasks.push(async move {
5170                // Create mapper with configured lease duration
5171                let mut mapper = UpnpPortMapper::with_lease_duration(
5172                    local_addr,
5173                    this.config.upnp_lease_duration,
5174                );
5175
5176                // Try to discover gateway
5177                if let Err(e) = mapper.discover_with_timeout(timeout).await {
5178                    trace!("UPnP discovery failed for {}: {}", local_addr, e);
5179                    return;
5180                }
5181
5182                // Try to add port mapping (0 = use same port as local)
5183                match mapper.add_mapping(0).await {
5184                    Ok(external_addr) => {
5185                        // Check if UPnP returned a private IP (double-NAT scenario)
5186                        let is_private = is_private_ip(&external_addr.ip());
5187
5188                        // Final address for the candidate
5189                        let candidate_addr = if is_private {
5190                            if let Some(public_ip) = stun_ip {
5191                                let mut addr = external_addr;
5192                                addr.set_ip(public_ip);
5193                                debug!(
5194                                    "UPnP double-NAT detected: {} is private, using STUN public IP {} -> {}",
5195                                    external_addr.ip(),
5196                                    public_ip,
5197                                    addr
5198                                );
5199                                addr
5200                            } else {
5201                                debug!(
5202                                    "UPnP returned private IP {} but no STUN public IP available",
5203                                    external_addr.ip()
5204                                );
5205                                external_addr
5206                            }
5207                        } else {
5208                            external_addr
5209                        };
5210
5211                        // Create server reflexive candidate for the mapping
5212                        let candidate =
5213                            IceCandidate::server_reflexive(local_addr, candidate_addr, 1);
5214                        this.push_candidate(candidate);
5215
5216                        // Store mapper for later cleanup
5217                        this.upnp_mappers.lock().push(mapper);
5218
5219                        debug!(
5220                            "UPnP candidate gathered: {} -> {}",
5221                            local_addr, candidate_addr
5222                        );
5223                    }
5224                    Err(e) => {
5225                        debug!("UPnP mapping failed for {}: {}", local_addr, e);
5226                    }
5227                }
5228            });
5229        }
5230
5231        while tasks.next().await.is_some() {}
5232
5233        Ok(())
5234    }
5235
5236    async fn gather_servers(&self) -> RtcResult<()> {
5237        let mut tasks = FuturesUnordered::new();
5238
5239        for server in &self.config.ice_servers {
5240            for url in &server.urls {
5241                let server = server.clone();
5242                let url = url.clone();
5243                let this = self.clone();
5244
5245                tasks.push(async move {
5246                    let uri = match IceServerUri::parse(&url) {
5247                        Ok(uri) => uri,
5248                        Err(err) => {
5249                            debug!("invalid ICE server URI {}: {}", url, err);
5250                            return;
5251                        }
5252                    };
5253
5254                    match uri.kind {
5255                        IceUriKind::Stun => {
5256                            if this.config.ice_transport_policy == IceTransportPolicy::All {
5257                                match this.probe_stun(&uri).await {
5258                                    Ok(Some(candidate)) => this.push_candidate(candidate),
5259                                    Ok(None) => {}
5260                                    Err(e) => debug!(
5261                                        label = this.config.label.as_deref().unwrap_or("-"),
5262                                        "STUN probe failed for {}: {}", url, e
5263                                    ),
5264                                }
5265                            }
5266                        }
5267                        IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
5268                            Ok(Some(candidate)) => this.push_candidate(candidate),
5269                            Ok(None) => {}
5270                            Err(e) => debug!(
5271                                label = this.config.label.as_deref().unwrap_or("-"),
5272                                "TURN probe failed for {}: {}", url, e
5273                            ),
5274                        },
5275                    }
5276                });
5277            }
5278        }
5279
5280        while tasks.next().await.is_some() {}
5281        Ok(())
5282    }
5283
5284    /// Gather server candidates and return the first public IP discovered via STUN.
5285    /// This is used to detect and fix double-NAT scenarios for UPnP.
5286    async fn gather_servers_and_get_public_ip(&self) -> Option<IpAddr> {
5287        let mut tasks = FuturesUnordered::new();
5288        let public_ip: Arc<crate::platform::sync::Mutex<Option<IpAddr>>> =
5289            Arc::new(crate::platform::sync::Mutex::new(None));
5290
5291        for server in &self.config.ice_servers {
5292            for url in &server.urls {
5293                let server = server.clone();
5294                let url = url.clone();
5295                let this = self.clone();
5296                let public_ip_clone = public_ip.clone();
5297
5298                tasks.push(async move {
5299                    let uri = match IceServerUri::parse(&url) {
5300                        Ok(uri) => uri,
5301                        Err(err) => {
5302                            debug!("invalid ICE server URI {}: {}", url, err);
5303                            return;
5304                        }
5305                    };
5306
5307                    match uri.kind {
5308                        IceUriKind::Stun => {
5309                            if this.config.ice_transport_policy == IceTransportPolicy::All {
5310                                match this.probe_stun(&uri).await {
5311                                    Ok(Some(candidate)) => {
5312                                        // Capture public IP if it's not private
5313                                        if !is_private_ip(&candidate.address.ip()) {
5314                                            let mut ip = public_ip_clone.lock();
5315                                            if ip.is_none() {
5316                                                *ip = Some(candidate.address.ip());
5317                                            }
5318                                        }
5319                                        this.push_candidate(candidate);
5320                                    }
5321                                    Ok(None) => {}
5322                                    Err(e) => debug!(
5323                                        label = this.config.label.as_deref().unwrap_or("-"),
5324                                        "STUN probe failed for {}: {}", url, e
5325                                    ),
5326                                }
5327                            }
5328                        }
5329                        IceUriKind::Turn => match this.probe_turn(&uri, &server).await {
5330                            Ok(Some(candidate)) => this.push_candidate(candidate),
5331                            Ok(None) => {}
5332                            Err(e) => debug!(
5333                                label = this.config.label.as_deref().unwrap_or("-"),
5334                                "TURN probe failed for {}: {}", url, e
5335                            ),
5336                        },
5337                    }
5338                });
5339            }
5340        }
5341
5342        while tasks.next().await.is_some() {}
5343        let ip = *public_ip.lock();
5344        if let Some(ip) = &ip {
5345            debug!("STUN public IP for UPnP double-NAT detection: {}", ip);
5346        } else {
5347            debug!("No STUN public IP available for UPnP double-NAT detection");
5348        }
5349        ip
5350    }
5351
5352    async fn probe_stun(&self, uri: &IceServerUri) -> RtcResult<Option<IceCandidate>> {
5353        let addr = uri.resolve(self.config.disable_ipv6).await?;
5354
5355        // Find a suitable host address to bind to (prefer non-loopback IPv4).
5356        // Prefer the candidate's `related_address` (the address the socket was
5357        // actually bound on): with `external_ip` configured, the candidate
5358        // `address` carries the advertised NAT/EIP IP which is NOT assigned to
5359        // any local interface — binding on it fails with EADDRNOTAVAIL for
5360        // every port in the RTP range.
5361        let pick_bind_ip = |c: &IceCandidate| -> Option<IpAddr> {
5362            let base = c.related_address.unwrap_or(c.address);
5363            match base.ip() {
5364                IpAddr::V4(ip) if !addr.is_ipv6() && !ip.is_loopback() && !ip.is_unspecified() => {
5365                    Some(IpAddr::V4(ip))
5366                }
5367                IpAddr::V6(ip) if addr.is_ipv6() && !ip.is_loopback() && !ip.is_unspecified() => {
5368                    Some(IpAddr::V6(ip))
5369                }
5370                _ => None,
5371            }
5372        };
5373        let fallback = if addr.is_ipv6() {
5374            IpAddr::V6(core::net::Ipv6Addr::UNSPECIFIED)
5375        } else {
5376            IpAddr::V4(core::net::Ipv4Addr::new(0, 0, 0, 0))
5377        };
5378        let bind_ip = self
5379            .local_candidates
5380            .lock()
5381            .iter()
5382            .filter(|c| c.typ == IceCandidateType::Host)
5383            .filter_map(pick_bind_ip)
5384            .next()
5385            .unwrap_or(fallback);
5386
5387        let socket = match uri.transport {
5388            IceTransportProtocol::Udp => self.bind_socket(bind_ip).await?,
5389            IceTransportProtocol::Tcp => self.bind_socket(bind_ip).await?,
5390        };
5391        let local_addr = socket.local_addr()?;
5392        let tx_id = random_bytes::<12>();
5393        let message = StunMessage::binding_request(tx_id, Some("rustrtc"));
5394        let bytes = message.encode(None, true)?;
5395        socket.send_to(&bytes, addr).await?;
5396        let mut buf = [0u8; MAX_STUN_MESSAGE];
5397        let (len, from) = with_timeout(self.config.stun_timeout, socket.recv_from(&mut buf))
5398            .await
5399            .map_err(|_| RtcError::Internal("stun recv timeout".into()))??;
5400        if from.ip() != addr.ip() {
5401            return Ok(None);
5402        }
5403        let parsed = StunMessage::decode(&buf[..len])?;
5404        if let Some(mapped) = parsed.xor_mapped_address {
5405            self.register_bound(&socket);
5406            return Ok(Some(IceCandidate::server_reflexive(local_addr, mapped, 1)));
5407        }
5408        Ok(None)
5409    }
5410
5411    async fn probe_turn(
5412        &self,
5413        uri: &IceServerUri,
5414        server: &IceServer,
5415    ) -> RtcResult<Option<IceCandidate>> {
5416        let credentials = TurnCredentials::from_server(server)?;
5417        let client = TurnClient::connect(uri, self.config.disable_ipv6).await?;
5418        let allocation = client.allocate(credentials).await?;
5419        let relayed_addr = allocation.relayed_address;
5420        debug!(
5421            "TURN allocation granted: relayed={}, lifetime={}s",
5422            relayed_addr, allocation.lifetime_secs
5423        );
5424
5425        let client = Arc::new(client);
5426        self.turn_clients
5427            .lock()
5428            .insert(relayed_addr, client.clone());
5429        let _ = self
5430            .socket_tx
5431            .send(IceSocketWrapper::Turn(client, relayed_addr));
5432
5433        Ok(Some(IceCandidate::relay(
5434            relayed_addr,
5435            1,
5436            allocation.transport.as_str(),
5437        )))
5438    }
5439
5440    fn push_candidate(&self, mut candidate: IceCandidate) {
5441        if self.config.disable_ipv6 && candidate.address.is_ipv6() {
5442            return;
5443        }
5444        // mDNS obfuscation (draft-ietf-rtcweb-mdns): host candidates advertise
5445        // our `<random>.local` hostname in SDP; the real address stays
5446        // internal so pairing and connectivity checks are unaffected.
5447        if candidate.typ == IceCandidateType::Host
5448            && let Some(hostname) = self
5449                .transport_inner
5450                .lock()
5451                .as_ref()
5452                .and_then(|weak| weak.upgrade())
5453                .and_then(|inner| inner.mdns_hostname.clone())
5454        {
5455            candidate = candidate.with_hostname(hostname);
5456        }
5457        let mut candidates = self.local_candidates.lock();
5458        if candidates.iter().any(|c| c.address == candidate.address) {
5459            return;
5460        }
5461        tracing::debug!(
5462            "Gathered local candidate: {} type={:?}",
5463            candidate.address,
5464            candidate.typ
5465        );
5466        candidates.push(candidate.clone());
5467        drop(candidates);
5468        let _ = self.candidate_tx.send(candidate);
5469    }
5470}
5471
5472#[derive(Debug, Clone, PartialEq, Eq)]
5473pub(crate) struct IceServerUri {
5474    kind: IceUriKind,
5475    host: String,
5476    port: u16,
5477    transport: IceTransportProtocol,
5478}
5479
5480impl IceServerUri {
5481    fn parse(input: &str) -> RtcResult<Self> {
5482        let (scheme, rest) = input
5483            .split_once(':')
5484            .ok_or_else(|| RtcError::Internal(format!("missing scheme")))?;
5485        let (host_part, query) = match rest.split_once('?') {
5486            Some(parts) => parts,
5487            None => (rest, ""),
5488        };
5489        let (host, port) = if let Some((h, p)) = host_part.rsplit_once(':') {
5490            let port = p
5491                .parse::<u16>()
5492                .map_err(|e| RtcError::Internal(format!("invalid port: {e}")))?;
5493            (h.to_string(), port)
5494        } else {
5495            (host_part.to_string(), default_port_for_scheme(scheme)?)
5496        };
5497        let mut transport = default_transport_for_scheme(scheme)?;
5498        if !query.is_empty() {
5499            for pair in query.split('&') {
5500                if let Some((k, v)) = pair.split_once('=')
5501                    && k == "transport"
5502                {
5503                    transport = match v.to_ascii_lowercase().as_str() {
5504                        "udp" => IceTransportProtocol::Udp,
5505                        "tcp" => IceTransportProtocol::Tcp,
5506                        other => {
5507                            return Err(RtcError::Internal(format!(
5508                                "unsupported transport {}",
5509                                other
5510                            )));
5511                        }
5512                    };
5513                }
5514            }
5515        }
5516        if scheme.starts_with("stun") && query.contains("transport") {
5517            return Err(RtcError::Internal(format!(
5518                "stun URI must not include transport parameter"
5519            )));
5520        }
5521        let kind = match scheme {
5522            "stun" | "stuns" => IceUriKind::Stun,
5523            "turn" | "turns" => IceUriKind::Turn,
5524            other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
5525        };
5526        Ok(Self {
5527            kind,
5528            host,
5529            port,
5530            transport,
5531        })
5532    }
5533
5534    async fn resolve(&self, disable_ipv6: bool) -> RtcResult<SocketAddr> {
5535        let target = format!("{}:{}", self.host, self.port);
5536        let addrs = with_timeout(
5537            Duration::from_secs(5),
5538            crate::platform::dns::lookup_host(&target),
5539        )
5540        .await
5541        .map_err(|_| RtcError::Internal(format!("DNS lookup timed out for {}", target)))??;
5542
5543        for addr in addrs {
5544            if disable_ipv6 && addr.is_ipv6() {
5545                continue;
5546            }
5547            return Ok(addr);
5548        }
5549        Err(RtcError::Internal(format!(
5550            "{} unresolved (disable_ipv6={})",
5551            self.host, disable_ipv6
5552        )))
5553    }
5554}
5555
5556#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5557enum IceUriKind {
5558    Stun,
5559    Turn,
5560}
5561
5562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5563pub(crate) enum IceTransportProtocol {
5564    Udp,
5565    Tcp,
5566}
5567
5568impl IceTransportProtocol {
5569    fn as_str(&self) -> &'static str {
5570        match self {
5571            IceTransportProtocol::Udp => "udp",
5572            IceTransportProtocol::Tcp => "tcp",
5573        }
5574    }
5575}
5576
5577fn default_port_for_scheme(scheme: &str) -> RtcResult<u16> {
5578    Ok(match scheme {
5579        "stun" | "turn" => 3478,
5580        "stuns" | "turns" => 5349,
5581        other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
5582    })
5583}
5584
5585fn default_transport_for_scheme(scheme: &str) -> RtcResult<IceTransportProtocol> {
5586    Ok(match scheme {
5587        "stun" | "turn" => IceTransportProtocol::Udp,
5588        "stuns" | "turns" => IceTransportProtocol::Tcp,
5589        other => return Err(RtcError::Internal(format!("unsupported scheme {}", other))),
5590    })
5591}
5592
5593/// Check if an IP address is a private/internal address (not publicly routable)
5594fn is_private_ip(ip: &IpAddr) -> bool {
5595    match ip {
5596        IpAddr::V4(ipv4) => {
5597            let octets = ipv4.octets();
5598            // 10.0.0.0/8
5599            octets[0] == 10
5600                // 172.16.0.0/12
5601                || (octets[0] == 172 && (16..=31).contains(&octets[1]))
5602                // 192.168.0.0/16
5603                || (octets[0] == 192 && octets[1] == 168)
5604                // 169.254.0.0/16 (link-local)
5605                || (octets[0] == 169 && octets[1] == 254)
5606                // 127.0.0.0/8 (loopback)
5607                || octets[0] == 127
5608        }
5609        IpAddr::V6(ipv6) => {
5610            // IPv6 unique local fc00::/7
5611            ipv6.segments()[0] & 0xfe00 == 0xfc00
5612                // IPv6 link-local fe80::/10
5613                || ipv6.segments()[0] & 0xffc0 == 0xfe80
5614                // IPv6 loopback ::1
5615                || *ipv6 == core::net::Ipv6Addr::LOCALHOST
5616        }
5617    }
5618}
5619
5620fn hex_encode(bytes: &[u8]) -> String {
5621    const TABLE: &[u8; 16] = b"0123456789abcdef";
5622    let mut out = String::with_capacity(bytes.len() * 2);
5623    for byte in bytes {
5624        out.push(TABLE[(byte >> 4) as usize] as char);
5625        out.push(TABLE[(byte & 0x0f) as usize] as char);
5626    }
5627    out
5628}
5629
5630#[derive(Clone)]
5631pub enum IceSocketWrapper {
5632    Platform(Arc<dyn crate::platform::net::UdpSocket>),
5633    /// std: the tokio UDP socket; no_std: never constructed (the placeholder
5634    /// type keeps the enum shape).
5635    Udp(Arc<UdpSocket>),
5636    /// Shared (muxed) UDP socket. Incoming packets arrive via the handle's
5637    /// receiver (fed by the shared demux loop); outbound packets go out through
5638    /// the handle, which also records the destination for reverse routing.
5639    SharedUdp(Arc<shared_udp::SharedUdpHandle>),
5640    #[cfg(feature = "std")]
5641    TcpListener(Arc<TcpListener>),
5642    #[cfg(feature = "std")]
5643    TcpStream(
5644        Arc<Mutex<TcpReadHalf>>,
5645        Arc<Mutex<TcpWriteHalf>>,
5646        SocketAddr,
5647    ),
5648    Turn(Arc<TurnClient>, SocketAddr),
5649}
5650
5651impl core::fmt::Debug for IceSocketWrapper {
5652    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
5653        f.write_str(&self.diag())
5654    }
5655}
5656
5657impl IceSocketWrapper {
5658    /// Local address of the underlying socket (best effort: TURN/TCP
5659    /// variants report the relay/listen endpoint where meaningful).
5660    pub fn local_addr(&self) -> RtcResult<SocketAddr> {
5661        match self {
5662            IceSocketWrapper::Platform(s) => s.local_addr().map_err(RtcError::from),
5663            IceSocketWrapper::Udp(s) => s.local_addr().map_err(RtcError::from),
5664            IceSocketWrapper::SharedUdp(h) => h.local_addr(),
5665            #[cfg(feature = "std")]
5666            IceSocketWrapper::TcpListener(l) => l.local_addr().map_err(RtcError::from),
5667            #[cfg(feature = "std")]
5668            IceSocketWrapper::TcpStream(_, _, peer) => Ok(*peer),
5669            IceSocketWrapper::Turn(_, addr) => Ok(*addr),
5670        }
5671    }
5672
5673    /// Short description for diagnostic logs (no async I/O).
5674    pub fn diag(&self) -> String {
5675        match self {
5676            IceSocketWrapper::Platform(s) => format!(
5677                "platform-udp:{}",
5678                s.local_addr()
5679                    .map(|a| a.to_string())
5680                    .unwrap_or_else(|_| "?".into())
5681            ),
5682            IceSocketWrapper::Udp(s) => format!(
5683                "udp:{}",
5684                s.local_addr()
5685                    .map(|a| a.to_string())
5686                    .unwrap_or_else(|_| "?".into())
5687            ),
5688            IceSocketWrapper::SharedUdp(h) => format!(
5689                "udp-mux:{}",
5690                h.local_addr()
5691                    .map(|a| a.to_string())
5692                    .unwrap_or_else(|_| "?".into())
5693            ),
5694            #[cfg(feature = "std")]
5695            IceSocketWrapper::TcpListener(l) => format!(
5696                "tcp-listen:{}",
5697                l.local_addr()
5698                    .map(|a| a.to_string())
5699                    .unwrap_or_else(|_| "?".into())
5700            ),
5701            #[cfg(feature = "std")]
5702            IceSocketWrapper::TcpStream(_, _, peer) => format!("tcp-stream:peer={peer}"),
5703            IceSocketWrapper::Turn(_, addr) => format!("turn:{addr}"),
5704        }
5705    }
5706
5707    /// Non-blocking variant of `send_to`: calls `try_send_to` once and returns
5708    /// immediately on `WouldBlock` / `ENOBUFS` instead of parking on
5709    /// `writable()`. Used by the RTP bridge fast-path.
5710    pub fn try_send_to(&self, data: &[u8], addr: SocketAddr) -> RtcResult<usize> {
5711        match self {
5712            IceSocketWrapper::Platform(s) => {
5713                match <dyn crate::platform::net::UdpSocket>::try_send_to(
5714                    s.as_ref(),
5715                    data,
5716                    addr,
5717                ) {
5718                    Ok(len) => Ok(len),
5719                    Err(e) => Err(RtcError::Internal(alloc::format!(
5720                        "platform try_send_to: {e}"
5721                    ))),
5722                }
5723            }
5724            IceSocketWrapper::Udp(s) => match s.try_send_to(data, addr) {
5725                Ok(len) => Ok(len),
5726                Err(e) => {
5727                    let reason = match s.local_addr() {
5728                        Ok(local) => format!("UDP {} -> {} failed: {}", local, addr, e),
5729                        Err(_) => format!("UDP -> {} failed: {}", addr, e),
5730                    };
5731                    Err(RtcError::Internal(reason))
5732                }
5733            },
5734            #[cfg(not(feature = "std"))]
5735            IceSocketWrapper::Udp(_) => {
5736                unimplemented!("direct UDP sockets are std-only")
5737            }
5738            IceSocketWrapper::SharedUdp(h) => {
5739                // Shared (muxed) UDP is still a synchronous datagram socket:
5740                // record the peer for reverse routing, then write without
5741                // parking (same contract as the `Udp` arm above).
5742                h.register_peer(addr);
5743                match <dyn crate::platform::net::UdpSocket>::try_send_to(
5744                    h.socket().as_ref(),
5745                    data,
5746                    addr,
5747                ) {
5748                    Ok(len) => Ok(len),
5749                    Err(e) => {
5750                        let reason = match h.local_addr() {
5751                            Ok(local) => format!("shared UDP {} -> {} failed: {}", local, addr, e),
5752                            Err(_) => format!("shared UDP -> {} failed: {}", addr, e),
5753                        };
5754                        Err(RtcError::Internal(reason))
5755                    }
5756                }
5757            }
5758            // TURN / TCP / TLS have no synchronous send: callers must use the
5759            // async `send_to` (the bridge fast-path queues via `IceConn`).
5760            _ => Err(RtcError::Internal(format!(
5761                "IceSocketWrapper::try_send_to not supported for this transport variant"
5762            ))),
5763        }
5764    }
5765
5766    pub async fn send_to(&self, data: &[u8], addr: SocketAddr) -> RtcResult<usize> {
5767        match self {
5768            IceSocketWrapper::Platform(s) => s
5769                .send_to(data, addr)
5770                .await
5771                .map_err(|e| RtcError::Internal(format!("platform send_to: {e}"))),
5772            #[cfg(feature = "std")]
5773            IceSocketWrapper::Udp(s) => loop {
5774                match s.try_send_to(data, addr) {
5775                    Ok(len) => return Ok(len),
5776                    Err(e) if e.kind() == ErrorKind::WouldBlock => {
5777                        s.writable().await?;
5778                        continue;
5779                    }
5780                    Err(e) => {
5781                        if let Some(code) = e.raw_os_error()
5782                            && code == 55
5783                        {
5784                            s.writable().await?;
5785                            continue;
5786                        }
5787                        let reason = RtcError::Internal(format!(
5788                            "UDP {} -> {} failed: {}",
5789                            s.local_addr()?,
5790                            addr,
5791                            e
5792                        ));
5793                        return Err(reason);
5794                    }
5795                }
5796            },
5797            #[cfg(not(feature = "std"))]
5798            IceSocketWrapper::Udp(_) => {
5799                unimplemented!("direct UDP sockets are std-only")
5800            }
5801            IceSocketWrapper::SharedUdp(h) => {
5802                let dest = addr;
5803                h.send_to(data, dest).await.map_err(RtcError::from)
5804            }
5805            #[cfg(feature = "std")]
5806            IceSocketWrapper::TcpListener(_) => {
5807                return Err(RtcError::Internal(format!(
5808                    "send_to not supported on TcpListener"
5809                )));
5810            }
5811            #[cfg(feature = "std")]
5812            IceSocketWrapper::TcpStream(_, write, _) => {
5813                let len = data.len();
5814                if len > 0xFFFF {
5815                    return Err(RtcError::Internal(format!(
5816                        "STUN message too large for TCP framing"
5817                    )));
5818                }
5819                let header = (len as u16).to_be_bytes();
5820                let mut framed = Vec::with_capacity(2 + len);
5821                framed.extend_from_slice(&header);
5822                framed.extend_from_slice(data);
5823                tcp_write_all(write, &framed).await?;
5824                Ok(data.len())
5825            }
5826            IceSocketWrapper::Turn(c, _) => {
5827                if let Some(channel) = c.get_channel(addr).await {
5828                    c.send_channel_data(channel, data).await?;
5829                } else {
5830                    c.send_indication(addr, data).await?;
5831                }
5832                Ok(data.len())
5833            }
5834        }
5835    }
5836
5837    pub async fn recv_from(&self, buf: &mut [u8]) -> RtcResult<(usize, SocketAddr)> {
5838        match self {
5839            IceSocketWrapper::Platform(s) => s.recv_from(buf).await.map_err(|e| e.into()),
5840            IceSocketWrapper::Udp(s) => s.recv_from(buf).await.map_err(|e| e.into()),
5841            IceSocketWrapper::SharedUdp(h) => match h.recv().await {
5842                Some((data, addr)) => {
5843                    if data.len() > buf.len() {
5844                        return Err(RtcError::Internal(format!(
5845                            "shared UDP packet too large: {} > {}",
5846                            data.len(),
5847                            buf.len()
5848                        )));
5849                    }
5850                    let len = data.len();
5851                    buf[..len].copy_from_slice(&data);
5852                    Ok((len, addr))
5853                }
5854                None => Err(RtcError::Internal(format!("shared UDP channel closed"))),
5855            },
5856            #[cfg(feature = "std")]
5857            IceSocketWrapper::TcpStream(read, _, peer) => {
5858                #[cfg(feature = "std")]
5859                use tokio::io::AsyncReadExt;
5860                let mut stream = read.lock().await;
5861                let mut len_buf = [0u8; 2];
5862                stream.read_exact(&mut len_buf).await?;
5863                let len = u16::from_be_bytes(len_buf) as usize;
5864                if len > buf.len() {
5865                    return Err(RtcError::Internal(format!(
5866                        "TCP STUN message too large: {} > {}",
5867                        len,
5868                        buf.len()
5869                    )));
5870                }
5871                stream.read_exact(&mut buf[..len]).await?;
5872                Ok((len, *peer))
5873            }
5874            #[cfg(feature = "std")]
5875            IceSocketWrapper::TcpListener(_) => Err(RtcError::Internal(format!(
5876                "recv_from not supported on TcpListener wrapper directly"
5877            ))),
5878            IceSocketWrapper::Turn(_, _) => Err(RtcError::Internal(format!(
5879                "recv_from not supported on TURN wrapper directly"
5880            ))),
5881        }
5882    }
5883}
5884
5885#[cfg(all(test, feature = "std"))]
5886mod bind_socket_retry_tests {
5887    //! An EADDRINUSE port must be skipped in favor of the next one in the
5888    //! configured range (the old string check never matched the platform
5889    //! errno text "Address already in use" and failed the bind outright).
5890
5891    use super::*;
5892
5893    fn gatherer(start: u16, end: u16) -> IceGatherer {
5894        let (candidate_tx, _) = broadcast::channel(1);
5895        let (socket_tx, _socket_rx) = crate::platform::sync::mpsc::unbounded_channel();
5896        let mut config = RtcConfiguration::default();
5897        config.rtp_start_port = Some(start);
5898        config.rtp_end_port = Some(end);
5899        IceGatherer::new(config, candidate_tx, socket_tx)
5900    }
5901
5902    /// A socket actually occupying an even port (bind_socket scans even
5903    /// ports only).
5904    async fn occupy_even_port() -> (tokio::net::UdpSocket, u16) {
5905        loop {
5906            let s = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
5907            let p = s.local_addr().unwrap().port();
5908            if p % 2 == 0 {
5909                return (s, p);
5910            }
5911        }
5912    }
5913
5914    #[tokio::test]
5915    async fn skips_a_busy_port_and_binds_the_next_one() {
5916        // One busy even port; a two-port range must still yield the other.
5917        let (_blocker, busy) = occupy_even_port().await;
5918        let other = busy + 2;
5919
5920        let g = gatherer(busy, other);
5921        let socket = g
5922            .bind_socket(IpAddr::from([127, 0, 0, 1]))
5923            .await
5924            .expect("bind_socket must skip the busy port");
5925        assert_eq!(socket.local_addr().unwrap().port(), other);
5926    }
5927
5928    #[tokio::test]
5929    async fn exhausts_the_range_with_the_port_exhaustion_error() {
5930        // A single-port range whose port is busy reports exhaustion, not a
5931        // raw bind failure.
5932        let (_blocker, busy) = occupy_even_port().await;
5933
5934        let g = gatherer(busy, busy);
5935        let err = g
5936            .bind_socket(IpAddr::from([127, 0, 0, 1]))
5937            .await
5938            .expect_err("a fully busy range must fail");
5939        assert!(
5940            err.to_string().contains("No available even RTP ports"),
5941            "expected port exhaustion, got: {err}"
5942        );
5943    }
5944}