Skip to main content

rustrtc/transports/ice/
conn.rs

1use super::{IceSocketWrapper, should_drop_packet};
2use crate::errors::RtcResult;
3use crate::stats::{StatsEntry, StatsId, StatsKind, StatsProvider};
4use crate::transports::PacketReceiver;
5use anyhow::Result;
6use async_trait::async_trait;
7use bytes::Bytes;
8use parking_lot::{Mutex, RwLock};
9use serde_json::json;
10use std::net::SocketAddr;
11use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
12use std::sync::{Arc, OnceLock, Weak};
13use tokio::sync::{mpsc, watch};
14use tracing::{debug, trace, warn};
15
16/// Per-source-address state tracked during the latching probation period.
17///
18/// After `enable_latch_on_rtp()` is called, the first few RTP packets from
19/// potentially multiple source ports are observed before committing to a
20/// remote address.  This handles the common case where a stale NAT binding
21/// or a brief port-glitch delivers one or two packets from the wrong port
22/// before the real media stream settles.
23///
24/// Decision rules (evaluated in order on every new RTP packet):
25///
26/// 1. **Marker flush**: a candidate that has sent a packet with `marker=true`
27///    and has the lowest `first_seq` among candidates with a marker is
28///    selected immediately — the marker bit signals the first packet of a
29///    talkspurt and is a strong indicator of the real source.
30/// 2. **Consecutive dominance**: a candidate with `consecutive_count >= 2`
31///    that also has accumulated `>= 3` total packets across all observed
32///    candidates is selected.  Two sequential packets from the same port is
33///    a reliable signal.
34/// 3. **Timeout fallback**: after observing `max_packets` RTP
35///    packets without a clear winner, the candidate with the highest
36///    `packet_count` wins (ties broken by lowest `first_seq`).
37///
38/// Once latched the `probation` field is set to `None` so the `Mutex` is
39/// never locked again during the steady-state forwarding path.
40#[derive(Debug)]
41struct RtpCandidateState {
42    addr: SocketAddr,
43    #[allow(dead_code)]
44    ssrc: u32,
45    first_seq: u16,
46    last_seq: u16,
47    first_ts: u32,
48    packet_count: u8,
49    /// Number of RTP packets received with `seq == last_seq + 1`.
50    consecutive_count: u8,
51    has_marker: bool,
52}
53
54/// State held while we are in the probation / candidate-selection phase.
55#[derive(Debug)]
56struct RtpProbationState {
57    candidates: Vec<RtpCandidateState>,
58    total_packets: u8,
59    /// Maximum total observed packets before forcing a decision
60    /// (≈ 80 ms at 20 ms ptime when set to 6).
61    max_packets: u8,
62}
63
64pub struct IceConn {
65    pub socket_rx: watch::Receiver<Option<IceSocketWrapper>>,
66    rtcp_socket_rx: watch::Receiver<Option<IceSocketWrapper>>,
67    pub remote_addr: RwLock<SocketAddr>,
68    pub remote_rtcp_addr: RwLock<Option<SocketAddr>>,
69    pub dtls_receiver: RwLock<Option<Weak<dyn PacketReceiver>>>,
70    pub rtp_receiver: RwLock<Option<Weak<dyn PacketReceiver>>>,
71    pub latch_on_rtp: AtomicBool,
72    pub rtp_latched: AtomicBool,
73    pub rtcp_latched: AtomicBool,
74    pub expected_ssrc: AtomicU32,
75    pub rtp_rx_count: AtomicU64,
76    pub label: Option<String>,
77    /// First inbound RTP logged (INFO, once) — proves the remote is sending
78    /// media to us.
79    first_rtp_rx_logged: AtomicBool,
80    /// First inbound RTCP logged (INFO, once) — proves the remote's stack is
81    /// alive / acking.
82    first_rtcp_rx_logged: AtomicBool,
83    /// Set once the first outbound RTP has been written to the wire.
84    first_out_seen: AtomicBool,
85    /// Set once the "media flowing both directions" INFO has been emitted.
86    both_ways_logged: AtomicBool,
87    pub rx_packets: AtomicU64,
88    pub rx_bytes: AtomicU64,
89    /// DTLS datagrams dropped because no DTLS receiver was registered yet
90    /// (or it was torn down). Surfaced for handshake-stall diagnostics.
91    dtls_no_receiver_drops: AtomicU64,
92    pub tx_packets: AtomicU64,
93    pub tx_bytes: AtomicU64,
94    /// Candidate state during the brief probation window before latch commits.
95    /// Set to `Some` when `latch_on_rtp` is enabled, `None` once latched
96    /// (or when not using probation-mode latching).
97    probation: Mutex<Option<RtpProbationState>>,
98    /// Maximum packets to observe during probation.  `0` means "no probation"
99    /// — first SSRC-matching RTP latches immediately (legacy behaviour).
100    probation_max_packets: AtomicU8,
101    /// Lazily-initialised queue for transports without a synchronous send
102    /// (TURN / TCP / TLS): the RTP bridge fast-path hands packets here and a
103    /// spawned task drains them through the async `send_to`. Without this the
104    /// fast-path dropped every packet on Relay paths ("call connected but no
105    /// audio").
106    deferred_send_tx: OnceLock<mpsc::UnboundedSender<(IceSocketWrapper, Vec<u8>, SocketAddr)>>,
107}
108
109impl IceConn {
110    pub fn new(
111        socket_rx: watch::Receiver<Option<IceSocketWrapper>>,
112        remote_addr: SocketAddr,
113        label: Option<String>,
114    ) -> Arc<Self> {
115        Self::new_with_rtcp(socket_rx.clone(), socket_rx, remote_addr, label, None)
116    }
117
118    pub(crate) fn new_with_rtcp(
119        socket_rx: watch::Receiver<Option<IceSocketWrapper>>,
120        rtcp_socket_rx: watch::Receiver<Option<IceSocketWrapper>>,
121        remote_addr: SocketAddr,
122        label: Option<String>,
123        probation_max_packets: Option<u8>,
124    ) -> Arc<Self> {
125        Arc::new(Self {
126            socket_rx,
127            rtcp_socket_rx,
128            remote_addr: RwLock::new(remote_addr),
129            remote_rtcp_addr: RwLock::new(None),
130            dtls_receiver: RwLock::new(None),
131            rtp_receiver: RwLock::new(None),
132            latch_on_rtp: AtomicBool::new(false),
133            rtp_latched: AtomicBool::new(false),
134            rtcp_latched: AtomicBool::new(false),
135            expected_ssrc: AtomicU32::new(0),
136            rtp_rx_count: AtomicU64::new(0),
137            label,
138            first_rtp_rx_logged: AtomicBool::new(false),
139            first_rtcp_rx_logged: AtomicBool::new(false),
140            first_out_seen: AtomicBool::new(false),
141            both_ways_logged: AtomicBool::new(false),
142            rx_packets: AtomicU64::new(0),
143            rx_bytes: AtomicU64::new(0),
144            dtls_no_receiver_drops: AtomicU64::new(0),
145            tx_packets: AtomicU64::new(0),
146            tx_bytes: AtomicU64::new(0),
147            probation: Mutex::new(None),
148            probation_max_packets: AtomicU8::new(probation_max_packets.unwrap_or(0)),
149            deferred_send_tx: OnceLock::new(),
150        })
151    }
152
153    pub fn set_probation_max_packets(&self, max: Option<u8>) {
154        self.probation_max_packets
155            .store(max.unwrap_or(0), Ordering::Relaxed);
156    }
157
158    /// Record that the first outbound RTP was written to the wire. Called by
159    /// `RtpTransport` after its first successful send.
160    pub fn mark_first_outbound(&self) {
161        if self.first_out_seen.swap(true, Ordering::Relaxed) {
162            return;
163        }
164        let label_str = self.label.as_deref().unwrap_or("unknown");
165        tracing::trace!("IceConn: first outbound RTP sent label={}", label_str);
166        self.maybe_log_both_ways(label_str);
167    }
168
169    /// Single TRACE "media confirmed both directions" emitted once outbound and
170    /// inbound (RTP or RTCP) traffic have both been observed on this connection.
171    fn maybe_log_both_ways(&self, label_str: &str) {
172        if self.both_ways_logged.load(Ordering::Relaxed) {
173            return;
174        }
175        let inbound_seen = self.first_rtp_rx_logged.load(Ordering::Relaxed)
176            || self.first_rtcp_rx_logged.load(Ordering::Relaxed);
177        if self.first_out_seen.load(Ordering::Relaxed)
178            && inbound_seen
179            && self
180                .both_ways_logged
181                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
182                .is_ok()
183        {
184            tracing::trace!(
185                "IceConn: media confirmed both directions (outbound + inbound) label={}",
186                label_str
187            );
188        }
189    }
190
191    pub fn enable_latch_on_rtp(&self) {
192        self.latch_on_rtp.store(true, Ordering::Relaxed);
193        let max = self.probation_max_packets.load(Ordering::Relaxed);
194        if max > 0 {
195            // Initialise the probation state so candidate observation begins.
196            let mut p = self.probation.lock();
197            if p.is_none() {
198                *p = Some(RtpProbationState {
199                    candidates: Vec::new(),
200                    total_packets: 0,
201                    max_packets: max,
202                });
203            }
204        } else {
205            // max == 0 → immediate latch, no probation state
206            self.probation.lock().take();
207        }
208    }
209
210    /// Set the expected SSRC from the remote answer SDP.
211    /// When set, RTP latching uses SSRC match instead of source-address
212    /// mismatch, allowing latch to succeed even when NAT changes the port.
213    pub fn set_expected_ssrc(&self, ssrc: u32) {
214        self.expected_ssrc.store(ssrc, Ordering::Relaxed);
215    }
216
217    pub fn set_remote_rtcp_addr(&self, addr: Option<SocketAddr>) {
218        *self.remote_rtcp_addr.write() = addr;
219        self.rtcp_latched.store(false, Ordering::Relaxed);
220    }
221
222    /// Returns the local socket address (the bind address of the current
223    /// ICE socket). Returns `0.0.0.0:0` when the socket is not yet available
224    /// (e.g., during ICE candidate gathering).
225    pub fn local_addr(&self) -> SocketAddr {
226        let socket = self.socket_rx.borrow();
227        match socket.as_ref() {
228            Some(IceSocketWrapper::Udp(s)) => s
229                .local_addr()
230                .unwrap_or(SocketAddr::from(([0, 0, 0, 0], 0))),
231            Some(IceSocketWrapper::SharedUdp(h)) => h
232                .local_addr()
233                .unwrap_or(SocketAddr::from(([0, 0, 0, 0], 0))),
234            _ => SocketAddr::from(([0, 0, 0, 0], 0)),
235        }
236    }
237
238    pub(crate) fn set_remote_addr_from_selected_pair(
239        &self,
240        addr: SocketAddr,
241        reason: &'static str,
242    ) {
243        let current = *self.remote_addr.read();
244        if self.latch_on_rtp.load(Ordering::Relaxed)
245            && self.rtp_latched.load(Ordering::Relaxed)
246            && current != addr
247        {
248            warn!(
249                "IceConn: preserving latched RTP remote {} instead of selected-pair remote {} ({})",
250                current, addr, reason
251            );
252            return;
253        }
254
255        if current != addr {
256            debug!(
257                "IceConn: selected-pair remote changed {} -> {} ({})",
258                current, addr, reason
259            );
260        }
261        *self.remote_addr.write() = addr;
262    }
263
264    pub(crate) fn set_remote_addr_from_signaling(&self, addr: SocketAddr, reason: &'static str) {
265        self.reset_latch();
266        *self.remote_addr.write() = addr;
267        trace!(
268            "IceConn: signaling RTP remote set to {} ({}), latch reset",
269            addr, reason
270        );
271    }
272
273    /// Reset latching state before applying a remote SDP so a new source can
274    /// be selected. Clears both the latch flag and any in-progress probation.
275    pub fn reset_latch(&self) {
276        self.rtp_latched.store(false, Ordering::Relaxed);
277        self.rtcp_latched.store(false, Ordering::Relaxed);
278        let max = self.probation_max_packets.load(Ordering::Relaxed);
279        *self.probation.lock() = if self.latch_on_rtp.load(Ordering::Relaxed) && max > 0 {
280            Some(RtpProbationState {
281                candidates: Vec::new(),
282                total_packets: 0,
283                max_packets: max,
284            })
285        } else {
286            None
287        };
288    }
289
290    pub fn set_dtls_receiver(&self, receiver: Arc<dyn PacketReceiver>) {
291        *self.dtls_receiver.write() = Some(Arc::downgrade(&receiver));
292    }
293
294    pub fn set_rtp_receiver(&self, receiver: Arc<dyn PacketReceiver>) {
295        *self.rtp_receiver.write() = Some(Arc::downgrade(&receiver));
296    }
297
298    /// Non-blocking variant of `send`. Skips the `writable().await` parking
299    /// and simply returns `Err` when the kernel socket buffer is full. Used by
300    /// the RTP bridge fast-path so the receive loop never suspends on send.
301    pub fn try_send(&self, buf: &[u8]) -> Result<usize> {
302        if should_drop_packet() {
303            return Ok(buf.len());
304        }
305        let socket = self.socket_rx.borrow().clone();
306        let Some(socket) = socket else {
307            // Fallback: try the latest value
308            let mut socket_rx = self.socket_rx.clone();
309            let socket = socket_rx.borrow_and_update().clone();
310            let Some(socket) = socket else {
311                tracing::trace!("IceConn: try_send failed - no selected socket");
312                return Err(anyhow::anyhow!("No selected socket"));
313            };
314            return self.do_try_send(socket, buf);
315        };
316        self.do_try_send(socket, buf)
317    }
318
319    fn do_try_send(&self, socket: IceSocketWrapper, buf: &[u8]) -> Result<usize> {
320        let remote = *self.remote_addr.read();
321        if remote.port() == 0 {
322            return Err(anyhow::anyhow!("Remote address not set"));
323        }
324        match &socket {
325            IceSocketWrapper::Udp(_) | IceSocketWrapper::SharedUdp(_) => {
326                let n = socket.try_send_to(buf, remote)?;
327                // Note: tx_packets/tx_bytes not updated here (fast-path opt).
328                // These counters are informational and the write-order atomic
329                // would add unnecessary cost on the hot path.
330                Ok(n)
331            }
332            // TURN relay / TCP / TLS only expose async sends: queue the packet
333            // for a background task instead of failing (and dropping) forever.
334            _ => {
335                self.defer_async_send(socket, buf, remote);
336                Ok(buf.len())
337            }
338        }
339    }
340
341    /// Queue `buf` for asynchronous transmission on `socket`. The drain task is
342    /// spawned on first use and exits when `self` (and therefore the channel
343    /// sender) is dropped. Unbounded on purpose: this is the audio path, and
344    /// dropping on a full queue is exactly the failure mode being fixed.
345    fn defer_async_send(&self, socket: IceSocketWrapper, buf: &[u8], addr: SocketAddr) {
346        let tx = self.deferred_send_tx.get_or_init(|| {
347            let (tx, mut rx) = mpsc::unbounded_channel::<(IceSocketWrapper, Vec<u8>, SocketAddr)>();
348            tokio::spawn(async move {
349                while let Some((socket, buf, addr)) = rx.recv().await {
350                    if let Err(e) = socket.send_to(&buf, addr).await {
351                        debug!(
352                            error = %e,
353                            "IceConn: deferred async send failed (relay fast-path fallback)"
354                        );
355                    }
356                }
357            });
358            tx
359        });
360        if tx.send((socket, buf.to_vec(), addr)).is_err() {
361            trace!("IceConn: deferred send queue closed, dropping packet");
362        }
363    }
364
365    pub async fn send(&self, buf: &[u8]) -> Result<usize> {
366        if should_drop_packet() {
367            return Ok(buf.len());
368        }
369        let socket_rx = self.socket_rx.clone();
370        let socket_opt = socket_rx.borrow().clone();
371
372        if let Some(socket) = socket_opt {
373            let remote = *self.remote_addr.read();
374            if remote.port() == 0 {
375                return Err(anyhow::anyhow!("Remote address not set"));
376            }
377            let n = socket.send_to(buf, remote).await?;
378            self.tx_packets.fetch_add(1, Ordering::Relaxed);
379            self.tx_bytes.fetch_add(n as u64, Ordering::Relaxed);
380            Ok(n)
381        } else {
382            // Fallback: try to update if None
383            let mut socket_rx = self.socket_rx.clone();
384            let socket_opt = socket_rx.borrow_and_update().clone();
385            if let Some(socket) = socket_opt {
386                let remote = *self.remote_addr.read();
387                if remote.port() == 0 {
388                    return Err(anyhow::anyhow!("Remote address not set"));
389                }
390                let n = socket.send_to(buf, remote).await?;
391                self.tx_packets.fetch_add(1, Ordering::Relaxed);
392                self.tx_bytes.fetch_add(n as u64, Ordering::Relaxed);
393                Ok(n)
394            } else {
395                tracing::trace!("IceConn: send failed - no selected socket");
396                Err(anyhow::anyhow!("No selected socket"))
397            }
398        }
399    }
400
401    /// Send multiple DTLS records. On TCP, each record is RFC 4571-framed and all
402    /// frames are written in one syscall (avoids Chrome seeing a partial flight).
403    pub async fn send_dtls_record_batch(&self, records: &[Vec<u8>]) -> Result<usize> {
404        if records.is_empty() {
405            return Ok(0);
406        }
407        if should_drop_packet() {
408            return Ok(records.iter().map(|r| r.len()).sum());
409        }
410
411        let remote = *self.remote_addr.read();
412        if remote.port() == 0 {
413            return Err(anyhow::anyhow!("Remote address not set"));
414        }
415
416        let socket_rx = self.socket_rx.clone();
417        let mut socket_opt = socket_rx.borrow().clone();
418        if socket_opt.is_none() {
419            let mut rx = self.socket_rx.clone();
420            socket_opt = rx.borrow_and_update().clone();
421        }
422
423        let Some(socket) = socket_opt else {
424            tracing::trace!("IceConn: send_dtls_record_batch failed - no selected socket");
425            return Err(anyhow::anyhow!("No selected socket"));
426        };
427
428        let total_payload: usize = records.iter().map(|r| r.len()).sum();
429        self.tx_packets
430            .fetch_add(records.len() as u64, Ordering::Relaxed);
431
432        match &socket {
433            IceSocketWrapper::TcpStream(_, write, _) => {
434                let mut framed = Vec::new();
435                for record in records {
436                    if record.len() > 0xFFFF {
437                        return Err(anyhow::anyhow!("DTLS record too large for TCP framing"));
438                    }
439                    framed.extend_from_slice(&(record.len() as u16).to_be_bytes());
440                    framed.extend_from_slice(record);
441                }
442                super::tcp_write_all(write, &framed).await?;
443                self.tx_bytes
444                    .fetch_add(framed.len() as u64, Ordering::Relaxed);
445                Ok(total_payload)
446            }
447            _ => {
448                let mut total = 0usize;
449                for record in records {
450                    total += self.send(record).await?;
451                }
452                Ok(total)
453            }
454        }
455    }
456
457    pub async fn send_rtcp(&self, buf: &[u8]) -> Result<usize> {
458        let rtcp_addr = *self.remote_rtcp_addr.read();
459        let remote = if let Some(rtcp_addr) = rtcp_addr {
460            rtcp_addr
461        } else {
462            *self.remote_addr.read()
463        };
464
465        if remote.port() == 0 {
466            return Err(anyhow::anyhow!("Remote address not set"));
467        }
468
469        let mut socket_rx = if rtcp_addr.is_some() {
470            self.rtcp_socket_rx.clone()
471        } else {
472            self.socket_rx.clone()
473        };
474        let mut socket_opt = socket_rx.borrow().clone();
475        if socket_opt.is_none() {
476            socket_opt = socket_rx.borrow_and_update().clone();
477        }
478
479        if socket_opt.is_none() && rtcp_addr.is_some() {
480            let mut fallback_rx = self.socket_rx.clone();
481            socket_opt = fallback_rx.borrow().clone();
482            if socket_opt.is_none() {
483                socket_opt = fallback_rx.borrow_and_update().clone();
484            }
485        }
486
487        if let Some(socket) = socket_opt {
488            let n = socket.send_to(buf, remote).await?;
489            self.tx_packets.fetch_add(1, Ordering::Relaxed);
490            self.tx_bytes.fetch_add(n as u64, Ordering::Relaxed);
491            Ok(n)
492        } else {
493            tracing::trace!("IceConn: send_rtcp failed - no selected socket");
494            Err(anyhow::anyhow!("No selected socket"))
495        }
496    }
497}
498
499#[async_trait]
500impl PacketReceiver for IceConn {
501    async fn receive(&self, packet: Bytes, addr: SocketAddr, marshal_buf: &mut Vec<u8>) {
502        if packet.is_empty() {
503            return;
504        }
505
506        self.rx_packets.fetch_add(1, Ordering::Relaxed);
507        self.rx_bytes
508            .fetch_add(packet.len() as u64, Ordering::Relaxed);
509
510        let first_byte = packet[0];
511        // Scope for read lock
512        let current_remote = *self.remote_addr.read();
513
514        // Passive ICE-TCP: the browser often omits TCP candidates in SDP and
515        // connects inbound. Latch the real peer from the first packet on the
516        // accepted stream so DTLS/RTP replies use the correct destination.
517        let socket_is_inbound_tcp = {
518            let socket_rx = self.socket_rx.clone();
519            matches!(
520                socket_rx.borrow().as_ref(),
521                Some(IceSocketWrapper::TcpStream(_, _, _))
522            )
523        };
524        if current_remote.port() == 0 || (socket_is_inbound_tcp && current_remote != addr) {
525            *self.remote_addr.write() = addr;
526        } else if addr != current_remote {
527            // Note: We no longer automatically switch the remote address just by receiving
528            // a packet from a new source (e.g. DTLS). This prevents "path flapping"
529            // that can confuse the transport Layer. The remote address should only
530            // be updated via the ICE nomination process.
531            tracing::trace!(
532                "IceConn: Received packet from new address {:?} (byte={}) - ignoring address change",
533                addr,
534                first_byte
535            );
536        }
537
538        if (20..64).contains(&first_byte) {
539            // DTLS
540            let receiver = {
541                let rx_lock = self.dtls_receiver.read();
542                if let Some(rx) = &*rx_lock {
543                    rx.upgrade()
544                } else {
545                    None
546                }
547            };
548
549            if let Some(strong_rx) = receiver {
550                // tracing::trace!("IceConn: Forwarding DTLS packet to receiver");
551                strong_rx.receive(packet, addr, marshal_buf).await;
552            } else {
553                let drops = self.dtls_no_receiver_drops.fetch_add(1, Ordering::Relaxed) + 1;
554                debug!(
555                    drops,
556                    len = packet.len(),
557                    from = %addr,
558                    "IceConn: Received DTLS packet but no receiver registered — dropped"
559                );
560            }
561        } else if (128..192).contains(&first_byte) {
562            // RTP / RTCP
563            let is_rtcp = packet.len() >= 2 && (200..=211).contains(&packet[1]);
564
565            if self.latch_on_rtp.load(Ordering::Relaxed) {
566                if is_rtcp {
567                    // RTCP may teach the RTCP destination in non-mux mode, but it must
568                    // never override the RTP remote address.
569                    let mut remote_rtcp_addr = self.remote_rtcp_addr.write();
570                    if let Some(current_rtcp_remote) = *remote_rtcp_addr
571                        && addr != current_rtcp_remote
572                        && !self.rtcp_latched.load(Ordering::Relaxed)
573                    {
574                        *remote_rtcp_addr = Some(addr);
575                        self.rtcp_latched.store(true, Ordering::Relaxed);
576                    }
577                } else if !self.rtp_latched.load(Ordering::Relaxed) && packet.len() >= 12 {
578                    let expected = self.expected_ssrc.load(Ordering::Relaxed);
579                    let pkt_ssrc =
580                        u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]);
581
582                    let ssrc_ok = expected == 0 || pkt_ssrc == expected;
583
584                    if ssrc_ok {
585                        let seq = u16::from_be_bytes([packet[2], packet[3]]);
586                        let ts = u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]);
587                        let marker = (packet[1] & 0x80) != 0;
588
589                        let mut probation_guard = self.probation.lock();
590                        if let Some(ref mut prob) = *probation_guard {
591                            prob.total_packets = prob.total_packets.saturating_add(1);
592
593                            let pos = prob.candidates.iter().position(|c| c.addr == addr);
594                            if let Some(i) = pos {
595                                let c = &mut prob.candidates[i];
596                                if seq == c.last_seq.wrapping_add(1) {
597                                    c.consecutive_count = c.consecutive_count.saturating_add(1);
598                                } else {
599                                    // Non-sequential — reset run
600                                    c.consecutive_count = 0;
601                                }
602                                c.last_seq = seq;
603                                c.packet_count = c.packet_count.saturating_add(1);
604                                if marker {
605                                    c.has_marker = true;
606                                }
607                                if ts < c.first_ts {
608                                    c.first_ts = ts;
609                                }
610                                if seq < c.first_seq {
611                                    c.first_seq = seq;
612                                }
613                            } else {
614                                prob.candidates.push(RtpCandidateState {
615                                    addr,
616                                    ssrc: pkt_ssrc,
617                                    first_seq: seq,
618                                    last_seq: seq,
619                                    first_ts: ts,
620                                    packet_count: 1,
621                                    consecutive_count: 0,
622                                    has_marker: marker,
623                                });
624                            }
625
626                            if addr != current_remote {
627                                *self.remote_addr.write() = addr;
628                            }
629
630                            let total = prob.total_packets;
631                            let winner: Option<SocketAddr>;
632
633                            // Rule 1: candidate with marker=true and the
634                            // lowest first_seq wins immediately.
635                            let marker_winner = prob
636                                .candidates
637                                .iter()
638                                .filter(|c| c.has_marker)
639                                .min_by_key(|c| c.first_seq);
640
641                            if let Some(mw) = marker_winner {
642                                winner = Some(mw.addr);
643                            } else if total >= prob.max_packets {
644                                // Rule 3 (timeout fallback): pick the
645                                // candidate with the most packets; break
646                                // ties by lowest first_seq.
647                                winner = prob
648                                    .candidates
649                                    .iter()
650                                    .max_by(|a, b| {
651                                        a.packet_count
652                                            .cmp(&b.packet_count)
653                                            .then(b.first_seq.cmp(&a.first_seq))
654                                    })
655                                    .map(|c| c.addr);
656                            } else {
657                                // Rule 2: consecutive dominance — at
658                                // least 2 consecutive packets from one
659                                // source and at least 3 total observed.
660                                winner = if total >= 3 {
661                                    prob.candidates
662                                        .iter()
663                                        .find(|c| c.consecutive_count >= 2)
664                                        .map(|c| c.addr)
665                                } else {
666                                    None
667                                };
668                            }
669
670                            if let Some(win_addr) = winner {
671                                // Commit the latch.
672                                *probation_guard = None; // drop state
673                                drop(probation_guard);
674
675                                if win_addr != current_remote {
676                                    *self.remote_addr.write() = win_addr;
677                                }
678                                self.rtp_latched.store(true, Ordering::Relaxed);
679                                trace!(
680                                    "IceConn: RTP latched to {} after probation \
681                                         (expected_ssrc={}, total_obs={})",
682                                    win_addr, expected, total
683                                );
684                            }
685                        } else {
686                            // No probation state — immediate latch
687                            // (legacy path for callers that never called
688                            // `enable_latch_on_rtp`).
689                            if addr != current_remote {
690                                *self.remote_addr.write() = addr;
691                            }
692                            self.rtp_latched.store(true, Ordering::Relaxed);
693                            trace!(
694                                "IceConn: RTP latched to {} immediately \
695                                     (expected_ssrc={})",
696                                addr, expected
697                            );
698                        }
699                    }
700                }
701            }
702            let receiver = {
703                let rx_lock = self.rtp_receiver.read();
704                if let Some(rx) = &*rx_lock {
705                    rx.upgrade()
706                } else {
707                    None
708                }
709            };
710
711            if let Some(strong_rx) = receiver {
712                self.rtp_rx_count.fetch_add(1, Ordering::Relaxed);
713                let label_str = self.label.as_deref().unwrap_or("unknown");
714                if is_rtcp {
715                    if self
716                        .first_rtcp_rx_logged
717                        .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
718                        .is_ok()
719                    {
720                        tracing::debug!(
721                            "IceConn: first inbound RTCP ({} bytes) from {} label={} — remote alive",
722                            packet.len(),
723                            addr,
724                            label_str
725                        );
726                    }
727                } else if self
728                    .first_rtp_rx_logged
729                    .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
730                    .is_ok()
731                {
732                    tracing::debug!(
733                        "IceConn: first inbound RTP ({} bytes) from {} label={}",
734                        packet.len(),
735                        addr,
736                        label_str
737                    );
738                }
739                self.maybe_log_both_ways(label_str);
740                strong_rx.receive(packet, addr, marshal_buf).await;
741            } else {
742                trace!(
743                    "IceConn: No RTP receiver registered for packet from {}",
744                    addr
745                );
746            }
747        }
748    }
749}
750
751#[async_trait]
752impl StatsProvider for IceConn {
753    async fn collect(&self) -> RtcResult<Vec<StatsEntry>> {
754        let rx_packets = self.rx_packets.load(Ordering::Relaxed);
755        let rx_bytes = self.rx_bytes.load(Ordering::Relaxed);
756        let tx_packets = self.tx_packets.load(Ordering::Relaxed);
757        let tx_bytes = self.tx_bytes.load(Ordering::Relaxed);
758        let label = self.label.as_deref().unwrap_or("unknown");
759        let id = StatsId::new(format!("ice-conn-{}", label));
760        let entry = StatsEntry::new(id, StatsKind::Transport)
761            .with_value("label", json!(label))
762            .with_value("rxPackets", json!(rx_packets))
763            .with_value("rxBytes", json!(rx_bytes))
764            .with_value("txPackets", json!(tx_packets))
765            .with_value("txBytes", json!(tx_bytes));
766        Ok(vec![entry])
767    }
768}
769
770#[cfg(test)]
771mod tests {
772    use super::*;
773    use bytes::Bytes;
774    use std::net::{IpAddr, Ipv4Addr};
775    use tokio::io::AsyncReadExt;
776    use tokio::io::AsyncWriteExt;
777    use tokio::net::UdpSocket;
778    use tokio::sync::watch;
779
780    #[tokio::test]
781    async fn test_ice_conn_send_rtcp_mux() {
782        let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap();
783        let socket_wrapper = IceSocketWrapper::Udp(Arc::new(socket));
784        let (_tx, rx) = watch::channel(Some(socket_wrapper));
785
786        let receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
787        let receiver_addr = receiver.local_addr().unwrap();
788
789        let conn = IceConn::new(rx, receiver_addr, None);
790
791        // Send RTCP (via send_rtcp) -> should go to receiver_addr (default)
792        conn.send_rtcp(b"hello").await.unwrap();
793
794        let mut buf = [0u8; 1024];
795        let (len, _) = receiver.recv_from(&mut buf).await.unwrap();
796        assert_eq!(&buf[..len], b"hello");
797    }
798
799    /// Regression: the RTP bridge fast-path used to drop every packet on a
800    /// non-`Udp` transport because `IceSocketWrapper::try_send_to` returned
801    /// "not supported" (the "call connected but no audio" Relay-path bug).
802    /// Shared (muxed) UDP is such a transport — `try_send` must still deliver.
803    #[tokio::test]
804    async fn test_try_send_delivers_on_shared_udp_transport() {
805        // `acquire` keys the shared-port registry by the requested address, so
806        // reserve a concrete loopback port first instead of using port 0.
807        let probe = UdpSocket::bind("127.0.0.1:0").await.unwrap();
808        let bind_addr = probe.local_addr().unwrap();
809        drop(probe);
810
811        let (_local, handle, _guard) =
812            crate::transports::ice::shared_udp::acquire(bind_addr, "fastpath-test".to_string())
813                .await
814                .unwrap();
815        // tokio's `try_send_to` reports WouldBlock until the reactor has marked
816        // the socket writable; in production the socket is already actively
817        // receiving, so this only matters for the test's fresh socket.
818        let shared_socket = handle.socket().clone();
819        let (_tx, rx) = watch::channel(Some(IceSocketWrapper::SharedUdp(Arc::new(handle))));
820
821        let receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
822        let receiver_addr = receiver.local_addr().unwrap();
823        let conn = IceConn::new(rx, receiver_addr, None);
824
825        shared_socket.writable().await.unwrap();
826        conn.try_send(b"relayed")
827            .expect("fast-path send must not fail on shared UDP");
828
829        let mut buf = [0u8; 32];
830        let (len, _) = tokio::time::timeout(
831            std::time::Duration::from_millis(500),
832            receiver.recv_from(&mut buf),
833        )
834        .await
835        .expect("shared-UDP fast-path send must deliver")
836        .unwrap();
837        assert_eq!(&buf[..len], b"relayed");
838    }
839
840    /// Same regression for the transports that have **no** synchronous send
841    /// (TURN / TCP / TLS) — the production Relay-path case. `try_send` must
842    /// queue through the deferred async sender instead of returning Err.
843    #[tokio::test]
844    async fn test_try_send_defers_on_non_udp_transport() {
845        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
846        let listen_addr = listener.local_addr().unwrap();
847        let mut client = tokio::net::TcpStream::connect(listen_addr).await.unwrap();
848        let (server, peer) = listener.accept().await.unwrap();
849        let (read, write) = server.into_split();
850
851        let wrapper = IceSocketWrapper::TcpStream(
852            Arc::new(tokio::sync::Mutex::new(read)),
853            Arc::new(tokio::sync::Mutex::new(write)),
854            peer,
855        );
856        let (_tx, rx) = watch::channel(Some(wrapper));
857        let conn = IceConn::new(rx, peer, None);
858
859        // Non-UDP: must be accepted (queued for async send), not rejected.
860        conn.try_send(b"relayed")
861            .expect("fast-path send must not fail on non-UDP transport");
862
863        // The async drain task writes a 2-byte big-endian length prefix + data.
864        let mut header = [0u8; 2];
865        tokio::time::timeout(
866            std::time::Duration::from_millis(500),
867            tokio::io::AsyncReadExt::read_exact(&mut client, &mut header),
868        )
869        .await
870        .expect("deferred fast-path send must deliver")
871        .unwrap();
872        let n = u16::from_be_bytes(header) as usize;
873        let mut body = vec![0u8; n];
874        tokio::io::AsyncReadExt::read_exact(&mut client, &mut body)
875            .await
876            .unwrap();
877        assert_eq!(&body[..], b"relayed");
878    }
879
880    #[tokio::test]
881    async fn test_ice_conn_send_rtcp_no_mux() {
882        let rtp_socket = Arc::new(UdpSocket::bind("127.0.0.1:0").await.unwrap());
883        let rtp_socket_addr = rtp_socket.local_addr().unwrap();
884        let socket_wrapper = IceSocketWrapper::Udp(rtp_socket);
885        let (_tx, rx) = watch::channel(Some(socket_wrapper));
886
887        let rtcp_socket = Arc::new(UdpSocket::bind("127.0.0.1:0").await.unwrap());
888        let rtcp_socket_addr = rtcp_socket.local_addr().unwrap();
889        let rtcp_socket_wrapper = IceSocketWrapper::Udp(rtcp_socket);
890        let (_rtcp_tx, rtcp_rx) = watch::channel(Some(rtcp_socket_wrapper));
891
892        let rtp_receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
893        let rtp_addr = rtp_receiver.local_addr().unwrap();
894
895        let rtcp_receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
896        let rtcp_addr = rtcp_receiver.local_addr().unwrap();
897
898        let conn = IceConn::new_with_rtcp(rx, rtcp_rx, rtp_addr, None, None);
899        conn.set_remote_rtcp_addr(Some(rtcp_addr));
900
901        // Send RTP (via send) -> should go to rtp_addr
902        conn.send(b"rtp").await.unwrap();
903        let mut buf = [0u8; 1024];
904        let (len, rtp_src) = rtp_receiver.recv_from(&mut buf).await.unwrap();
905        assert_eq!(&buf[..len], b"rtp");
906        assert_eq!(rtp_src, rtp_socket_addr);
907
908        // Send RTCP (via send_rtcp) -> should go to rtcp_addr from the RTCP socket.
909        conn.send_rtcp(b"rtcp").await.unwrap();
910        let (len, rtcp_src) = rtcp_receiver.recv_from(&mut buf).await.unwrap();
911        assert_eq!(&buf[..len], b"rtcp");
912        assert_eq!(rtcp_src, rtcp_socket_addr);
913    }
914
915    #[tokio::test]
916    async fn test_changed_signaling_remote_resets_latch_and_retargets_send() {
917        let socket = Arc::new(UdpSocket::bind("127.0.0.1:0").await.unwrap());
918        let socket_wrapper = IceSocketWrapper::Udp(socket);
919        let (_tx, rx) = watch::channel(Some(socket_wrapper));
920
921        let old_receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
922        let old_addr = old_receiver.local_addr().unwrap();
923        let new_receiver = UdpSocket::bind("127.0.0.1:0").await.unwrap();
924        let new_addr = new_receiver.local_addr().unwrap();
925
926        let conn = IceConn::new(rx, old_addr, None);
927        conn.enable_latch_on_rtp();
928        conn.rtp_latched.store(true, Ordering::Relaxed);
929
930        conn.set_remote_addr_from_signaling(new_addr, "test SDP endpoint change");
931
932        assert_eq!(*conn.remote_addr.read(), new_addr);
933        assert!(!conn.rtp_latched.load(Ordering::Relaxed));
934
935        conn.send(b"retargeted").await.unwrap();
936        let mut buf = [0u8; 32];
937        let (len, _) = tokio::time::timeout(
938            std::time::Duration::from_millis(100),
939            new_receiver.recv_from(&mut buf),
940        )
941        .await
942        .expect("new signaling endpoint should receive RTP")
943        .unwrap();
944        assert_eq!(&buf[..len], b"retargeted");
945        assert!(
946            tokio::time::timeout(
947                std::time::Duration::from_millis(20),
948                old_receiver.recv_from(&mut buf),
949            )
950            .await
951            .is_err(),
952            "old signaling endpoint must stop receiving after retarget"
953        );
954    }
955
956    struct NoopReceiver;
957
958    #[async_trait]
959    impl PacketReceiver for NoopReceiver {
960        async fn receive(&self, _packet: Bytes, _addr: SocketAddr, _buf: &mut Vec<u8>) {}
961    }
962
963    #[tokio::test]
964    async fn test_ice_conn_latches_remote_addr_on_rtp() {
965        let (_tx, rx) = watch::channel(None);
966        let initial_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
967        let latched_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
968        let conn = IceConn::new(rx, initial_addr, None);
969        conn.enable_latch_on_rtp();
970        conn.set_rtp_receiver(Arc::new(NoopReceiver));
971
972        // Use a valid 12-byte RTP packet with marker=true (bit 7 of byte 1).
973        let pkt = Bytes::from_static(&[
974            0x80, 0x80, // V=2, M=1 (marker set)
975            0x00, 0x01, // seq=1
976            0x00, 0x00, 0x00, 0x01, // ts=1
977            0x00, 0x00, 0x00, 0x01, // ssrc=1
978        ]);
979        let mut marshal_buf = Vec::new();
980        conn.receive(pkt, latched_addr, &mut marshal_buf).await;
981
982        assert_eq!(*conn.remote_addr.read(), latched_addr);
983    }
984
985    #[tokio::test]
986    async fn test_rtcp_does_not_override_rtp_remote_addr() {
987        let (_tx, rx) = watch::channel(None);
988        let rtp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
989        let rtcp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4001);
990        let conn = IceConn::new(rx, rtp_addr, None);
991        conn.enable_latch_on_rtp();
992        conn.set_rtp_receiver(Arc::new(NoopReceiver));
993        conn.set_remote_rtcp_addr(Some(rtcp_addr));
994
995        let rtp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
996        // Use a valid 12-byte RTP packet with marker=true so probation resolves.
997        let rtp_pkt = Bytes::from_static(&[
998            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
999        ]);
1000        let mut marshal_buf = Vec::new();
1001        conn.receive(rtp_pkt, rtp_src, &mut marshal_buf).await;
1002        assert_eq!(*conn.remote_addr.read(), rtp_src);
1003        assert!(conn.rtp_latched.load(Ordering::Relaxed));
1004
1005        let rtcp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001);
1006        conn.receive(
1007            Bytes::from_static(&[0x80, 0xC8, 0x00, 0x00]),
1008            rtcp_src,
1009            &mut marshal_buf,
1010        )
1011        .await;
1012
1013        assert_eq!(
1014            *conn.remote_addr.read(),
1015            rtp_src,
1016            "RTCP should not override RTP remote address"
1017        );
1018    }
1019
1020    #[tokio::test]
1021    async fn test_rtcp_latches_rtcp_addr_in_non_mux_mode() {
1022        let (_tx, rx) = watch::channel(None);
1023        let rtp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
1024        let initial_rtcp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4001);
1025        let conn = IceConn::new(rx, rtp_addr, None);
1026        conn.enable_latch_on_rtp();
1027        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1028        conn.set_remote_rtcp_addr(Some(initial_rtcp_addr));
1029
1030        let rtp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
1031        let rtp_pkt = Bytes::from_static(&[
1032            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1033        ]);
1034        let mut marshal_buf = Vec::new();
1035        conn.receive(rtp_pkt, rtp_src, &mut marshal_buf).await;
1036
1037        let rtcp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001);
1038        conn.receive(
1039            Bytes::from_static(&[0x80, 0xC8, 0x00, 0x00]),
1040            rtcp_src,
1041            &mut marshal_buf,
1042        )
1043        .await;
1044
1045        assert_eq!(
1046            *conn.remote_rtcp_addr.read(),
1047            Some(rtcp_src),
1048            "RTCP should latch its own destination"
1049        );
1050        assert!(conn.rtcp_latched.load(Ordering::Relaxed));
1051    }
1052
1053    #[tokio::test]
1054    async fn test_rtcp_does_not_re_latch_after_locked() {
1055        let (_tx, rx) = watch::channel(None);
1056        let rtp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
1057        let conn = IceConn::new(rx, rtp_addr, None);
1058        conn.enable_latch_on_rtp();
1059        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1060        conn.set_remote_rtcp_addr(Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4001)));
1061
1062        let rtp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
1063        let rtp_pkt = Bytes::from_static(&[
1064            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1065        ]);
1066        let mut marshal_buf = Vec::new();
1067        conn.receive(rtp_pkt, rtp_src, &mut marshal_buf).await;
1068
1069        let rtcp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001);
1070        conn.receive(
1071            Bytes::from_static(&[0x80, 0xC8, 0x00, 0x00]),
1072            rtcp_src,
1073            &mut marshal_buf,
1074        )
1075        .await;
1076        assert_eq!(*conn.remote_rtcp_addr.read(), Some(rtcp_src));
1077
1078        let rogue_rtcp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 6001);
1079        conn.receive(
1080            Bytes::from_static(&[0x80, 0xC8, 0x00, 0x00]),
1081            rogue_rtcp_src,
1082            &mut marshal_buf,
1083        )
1084        .await;
1085
1086        assert_eq!(
1087            *conn.remote_rtcp_addr.read(),
1088            Some(rtcp_src),
1089            "RTCP should not re-latch after already latched"
1090        );
1091    }
1092
1093    #[tokio::test]
1094    async fn test_rtcp_ignored_in_mux_mode() {
1095        let (_tx, rx) = watch::channel(None);
1096        let rtp_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 4000);
1097        let conn = IceConn::new(rx, rtp_addr, None);
1098        conn.enable_latch_on_rtp();
1099        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1100
1101        let rtp_src = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
1102        let rtp_pkt = Bytes::from_static(&[
1103            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1104        ]);
1105        let mut marshal_buf = Vec::new();
1106        conn.receive(rtp_pkt, rtp_src, &mut marshal_buf).await;
1107        assert_eq!(*conn.remote_addr.read(), rtp_src);
1108
1109        conn.receive(
1110            Bytes::from_static(&[0x80, 0xC8, 0x00, 0x00]),
1111            rtp_src,
1112            &mut marshal_buf,
1113        )
1114        .await;
1115        assert_eq!(*conn.remote_addr.read(), rtp_src);
1116        assert!(
1117            conn.remote_rtcp_addr.read().is_none(),
1118            "RTCP address should remain None in mux mode"
1119        );
1120    }
1121
1122    #[tokio::test]
1123    async fn test_ssrc_based_latch_ignores_port_mismatch() {
1124        // Simulates the VoLTE/NAT scenario: the answer SDP advertises
1125        // remote port 4162, but real RTP arrives from port 17687.
1126        // Latching should succeed because the SSRC matches.
1127        let (_tx, rx) = watch::channel(None);
1128        let sdp_addr: SocketAddr = "10.17.230.54:4162".parse().unwrap();
1129        let real_addr: SocketAddr = "112.96.43.157:17687".parse().unwrap();
1130        let expected_ssrc: u32 = 787_088_145;
1131
1132        let conn = IceConn::new(rx, sdp_addr, None);
1133        conn.enable_latch_on_rtp();
1134        conn.set_expected_ssrc(expected_ssrc);
1135        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1136
1137        // Build a minimal 12-byte RTP packet with the matching SSRC.
1138        let mut pkt = vec![0x80u8, 0x00, 0x10, 0x98, 0x00, 0x00, 0x00, 0xa0];
1139        pkt.extend_from_slice(&expected_ssrc.to_be_bytes()); // bytes 8-11
1140
1141        // Send a second packet (seq=0x1099) to get consecutive_count=1,
1142        // then a third to trigger consecutive_count>=2 with total>=3.
1143        let mut pkt2 = vec![0x80u8, 0x00, 0x10, 0x99, 0x00, 0x00, 0x00, 0xa1];
1144        pkt2.extend_from_slice(&expected_ssrc.to_be_bytes());
1145        let mut marshal_buf = Vec::new();
1146        conn.receive(Bytes::from(pkt.clone()), real_addr, &mut marshal_buf)
1147            .await;
1148        conn.receive(Bytes::from(pkt2.clone()), real_addr, &mut marshal_buf)
1149            .await;
1150
1151        // Third packet: seq=0x109a
1152        let mut pkt3 = vec![0x80u8, 0x00, 0x10, 0x9a, 0x00, 0x00, 0x00, 0xa2];
1153        pkt3.extend_from_slice(&expected_ssrc.to_be_bytes());
1154        conn.receive(Bytes::from(pkt3), real_addr, &mut marshal_buf)
1155            .await;
1156
1157        assert_eq!(
1158            *conn.remote_addr.read(),
1159            real_addr,
1160            "Should latch to real NAT address when SSRC matches"
1161        );
1162        assert!(
1163            conn.rtp_latched.load(Ordering::Relaxed),
1164            "rtp_latched should be set after SSRC match"
1165        );
1166    }
1167
1168    #[tokio::test]
1169    async fn test_ssrc_based_latch_ignores_wrong_ssrc() {
1170        // A stray packet with a different SSRC should not trigger latching.
1171        let (_tx, rx) = watch::channel(None);
1172        let sdp_addr: SocketAddr = "10.17.230.54:4162".parse().unwrap();
1173        let rogue_addr: SocketAddr = "1.2.3.4:9999".parse().unwrap();
1174        let expected_ssrc: u32 = 787_088_145;
1175        let wrong_ssrc: u32 = 99_999_999;
1176
1177        let conn = IceConn::new(rx, sdp_addr, None);
1178        conn.enable_latch_on_rtp();
1179        conn.set_expected_ssrc(expected_ssrc);
1180        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1181
1182        let mut pkt = vec![0x80u8, 0x00, 0x10, 0x98, 0x00, 0x00, 0x00, 0xa0];
1183        pkt.extend_from_slice(&wrong_ssrc.to_be_bytes());
1184
1185        let mut marshal_buf = Vec::new();
1186        conn.receive(Bytes::from(pkt), rogue_addr, &mut marshal_buf)
1187            .await;
1188
1189        assert_eq!(
1190            *conn.remote_addr.read(),
1191            sdp_addr,
1192            "Should NOT latch when SSRC does not match"
1193        );
1194        assert!(
1195            !conn.rtp_latched.load(Ordering::Relaxed),
1196            "rtp_latched should remain false for wrong SSRC"
1197        );
1198    }
1199
1200    #[tokio::test]
1201    async fn test_address_based_latch_fallback_when_no_expected_ssrc() {
1202        // When no expected SSRC is configured, latching falls back to
1203        // the original address-mismatch logic (current behaviour).
1204        let (_tx, rx) = watch::channel(None);
1205        let initial_addr: SocketAddr = "10.0.0.1:4000".parse().unwrap();
1206        let new_addr: SocketAddr = "10.0.0.2:5000".parse().unwrap();
1207
1208        let conn = IceConn::new(rx, initial_addr, None);
1209        conn.enable_latch_on_rtp();
1210        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1211        // expected_ssrc stays 0 — no SDP SSRC hint
1212
1213        // Send 3 sequential packets to trigger consecutive_count >= 2
1214        for seq in 1u16..=3 {
1215            let pkt = Bytes::from(vec![
1216                0x80,
1217                0x00,
1218                (seq >> 8) as u8,
1219                seq as u8,
1220                0x00,
1221                0x00,
1222                0x00,
1223                seq as u8,
1224                0x00,
1225                0x00,
1226                0x01,
1227                0x23,
1228            ]);
1229            let mut marshal_buf = Vec::new();
1230            conn.receive(pkt, new_addr, &mut marshal_buf).await;
1231        }
1232
1233        assert_eq!(*conn.remote_addr.read(), new_addr);
1234        assert!(conn.rtp_latched.load(Ordering::Relaxed));
1235    }
1236
1237    // ── Probation-specific tests ───────────────────────────────────────────
1238
1239    /// Reproduce the Wireshark scenario: port 4114 sends seq=21466 first,
1240    /// then port 4014 sends seq=21465 with marker=true.  The latch MUST
1241    /// resolve to port 4014.
1242    #[tokio::test]
1243    async fn test_probation_marker_wins_over_first_arriving_packet() {
1244        let (_tx, rx) = watch::channel(None);
1245        let sdp_addr: SocketAddr = "223.104.80.120:4000".parse().unwrap();
1246        let port_4114: SocketAddr = "223.104.80.120:4114".parse().unwrap();
1247        let port_4014: SocketAddr = "223.104.80.120:4014".parse().unwrap();
1248        let ssrc: u32 = 0x6c0d_1ca5;
1249
1250        let conn = IceConn::new(rx, sdp_addr, None);
1251        conn.set_probation_max_packets(Some(6));
1252        conn.enable_latch_on_rtp();
1253        conn.set_expected_ssrc(ssrc);
1254        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1255
1256        // Frame 508072: port 4114, seq=21466, marker=false  (arrives first)
1257        let mut pkt_4114 = vec![0x80u8, 0x08, 0x53, 0xCA, 0x00, 0x00, 0x00, 0xA0];
1258        pkt_4114.extend_from_slice(&ssrc.to_be_bytes());
1259        let mut marshal_buf = Vec::new();
1260        conn.receive(Bytes::from(pkt_4114), port_4114, &mut marshal_buf)
1261            .await;
1262
1263        // Latch should NOT have fired yet (only 1 packet, no marker)
1264        assert!(
1265            !conn.rtp_latched.load(Ordering::Relaxed),
1266            "Should not latch after just one packet with no marker"
1267        );
1268
1269        // Frame 508078: port 4014, seq=21465 (lower!), marker=true  (real start)
1270        let mut pkt_4014 = vec![0x80u8, 0x88, 0x53, 0xC9, 0x00, 0x00, 0x00, 0xA0];
1271        pkt_4014.extend_from_slice(&ssrc.to_be_bytes());
1272        conn.receive(Bytes::from(pkt_4014), port_4014, &mut marshal_buf)
1273            .await;
1274
1275        assert!(
1276            conn.rtp_latched.load(Ordering::Relaxed),
1277            "Should latch after marker packet"
1278        );
1279        assert_eq!(
1280            *conn.remote_addr.read(),
1281            port_4014,
1282            "Should latch to port 4014 (marker=true), not port 4114 (first arrived)"
1283        );
1284    }
1285
1286    /// When a single source sends consecutively, it wins by dominance rule
1287    /// even without a marker bit.
1288    #[tokio::test]
1289    async fn test_probation_consecutive_dominance() {
1290        let (_tx, rx) = watch::channel(None);
1291        let sdp_addr: SocketAddr = "10.0.0.1:4000".parse().unwrap();
1292        let real_src: SocketAddr = "10.0.0.1:5000".parse().unwrap();
1293        let stray_src: SocketAddr = "10.0.0.1:5001".parse().unwrap();
1294
1295        let conn = IceConn::new(rx, sdp_addr, None);
1296        conn.set_probation_max_packets(Some(6));
1297        conn.enable_latch_on_rtp();
1298        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1299
1300        // One stray packet from a different port
1301        let stray = Bytes::from(vec![
1302            0x80, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x02,
1303        ]);
1304        let mut marshal_buf = Vec::new();
1305        conn.receive(stray, stray_src, &mut marshal_buf).await;
1306
1307        // Three consecutive packets from real source (seq 1,2,3)
1308        for seq in 1u16..=3 {
1309            let pkt = Bytes::from(vec![
1310                0x80, 0x00, 0x00, seq as u8, 0x00, 0x00, 0x00, seq as u8, 0x00, 0x00, 0x00, 0x01,
1311            ]);
1312            conn.receive(pkt, real_src, &mut marshal_buf).await;
1313        }
1314
1315        assert!(
1316            conn.rtp_latched.load(Ordering::Relaxed),
1317            "Should latch after consecutive dominance"
1318        );
1319        assert_eq!(
1320            *conn.remote_addr.read(),
1321            real_src,
1322            "Should latch to the source with consecutive packets"
1323        );
1324    }
1325
1326    /// After PROBATION_MAX_PACKETS total observations with no clear winner,
1327    /// the candidate with most packets wins (fallback rule).
1328    #[tokio::test]
1329    async fn test_probation_timeout_fallback_selects_dominant() {
1330        let (_tx, rx) = watch::channel(None);
1331        let sdp_addr: SocketAddr = "10.0.0.1:4000".parse().unwrap();
1332        let dominant: SocketAddr = "10.0.0.1:5000".parse().unwrap();
1333        let minor: SocketAddr = "10.0.0.1:5001".parse().unwrap();
1334
1335        let probation_max = 6u8;
1336        let conn = IceConn::new(rx, sdp_addr, None);
1337        conn.set_probation_max_packets(Some(probation_max));
1338        conn.enable_latch_on_rtp();
1339        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1340
1341        // Send 1 non-sequential packet from the minor source
1342        let minor_pkt = Bytes::from(vec![
1343            0x80, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x02,
1344        ]);
1345        let mut marshal_buf = Vec::new();
1346        conn.receive(minor_pkt, minor, &mut marshal_buf).await;
1347
1348        // Send (probation_max - 1) non-sequential packets from dominant
1349        for i in 0..(probation_max - 1) {
1350            // Use non-sequential seq values (skip every other) to avoid
1351            // triggering the consecutive-dominance rule.
1352            let seq = i * 2 + 10;
1353            let pkt = Bytes::from(vec![
1354                0x80, 0x00, 0x00, seq, 0x00, 0x00, 0x00, seq, 0x00, 0x00, 0x00, 0x01,
1355            ]);
1356            conn.receive(pkt, dominant, &mut marshal_buf).await;
1357        }
1358
1359        assert!(
1360            conn.rtp_latched.load(Ordering::Relaxed),
1361            "Should latch after PROBATION_MAX_PACKETS total packets"
1362        );
1363        assert_eq!(
1364            *conn.remote_addr.read(),
1365            dominant,
1366            "Should latch to source with most packets"
1367        );
1368    }
1369
1370    /// Once latched, subsequent packets from a different address must NOT
1371    /// change the latched remote.  Latch is sticky until reset_latch().
1372    #[tokio::test]
1373    async fn test_probation_latch_is_sticky_after_commit() {
1374        let (_tx, rx) = watch::channel(None);
1375        let sdp_addr: SocketAddr = "10.0.0.1:4000".parse().unwrap();
1376        let good_src: SocketAddr = "10.0.0.1:5000".parse().unwrap();
1377        let rogue_src: SocketAddr = "10.0.0.1:6000".parse().unwrap();
1378
1379        let conn = IceConn::new(rx, sdp_addr, None);
1380        conn.set_probation_max_packets(Some(6));
1381        conn.enable_latch_on_rtp();
1382        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1383
1384        // Latch to good_src via marker
1385        let pkt = Bytes::from(vec![
1386            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1387        ]);
1388        let mut marshal_buf = Vec::new();
1389        conn.receive(pkt, good_src, &mut marshal_buf).await;
1390        assert!(conn.rtp_latched.load(Ordering::Relaxed));
1391        assert_eq!(*conn.remote_addr.read(), good_src);
1392
1393        // Rogue source sends packets — addr must not change
1394        for seq in 2u8..=5 {
1395            let rogue_pkt = Bytes::from(vec![
1396                0x80, 0x00, 0x00, seq, 0x00, 0x00, 0x00, seq, 0x00, 0x00, 0x00, 0x99,
1397            ]);
1398            conn.receive(rogue_pkt, rogue_src, &mut marshal_buf).await;
1399        }
1400        assert_eq!(
1401            *conn.remote_addr.read(),
1402            good_src,
1403            "Latched address must not change after latch is committed"
1404        );
1405    }
1406
1407    /// reset_latch() clears the latch so a new source can be selected
1408    /// (used on re-INVITE).
1409    #[tokio::test]
1410    async fn test_reset_latch_allows_re_latching() {
1411        let (_tx, rx) = watch::channel(None);
1412        let sdp_addr: SocketAddr = "10.0.0.1:4000".parse().unwrap();
1413        let first_src: SocketAddr = "10.0.0.1:5000".parse().unwrap();
1414        let second_src: SocketAddr = "10.0.0.2:5000".parse().unwrap();
1415
1416        let conn = IceConn::new(rx, sdp_addr, None);
1417        conn.set_probation_max_packets(Some(6));
1418        conn.enable_latch_on_rtp();
1419        conn.set_rtp_receiver(Arc::new(NoopReceiver));
1420
1421        let pkt = Bytes::from(vec![
1422            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
1423        ]);
1424        let mut marshal_buf = Vec::new();
1425        conn.receive(pkt.clone(), first_src, &mut marshal_buf).await;
1426        assert_eq!(*conn.remote_addr.read(), first_src);
1427
1428        conn.reset_latch();
1429        assert!(!conn.rtp_latched.load(Ordering::Relaxed));
1430
1431        // After reset, the new source with marker should win
1432        let pkt2 = Bytes::from(vec![
1433            0x80, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
1434        ]);
1435        conn.receive(pkt2, second_src, &mut marshal_buf).await;
1436        assert_eq!(
1437            *conn.remote_addr.read(),
1438            second_src,
1439            "Should re-latch to new source after reset_latch()"
1440        );
1441    }
1442
1443    // ---------------------------------------------------------------------------
1444    // TCP / RFC 4571 framing tests
1445    // ---------------------------------------------------------------------------
1446
1447    /// Helper: bind a TCP listener and connect to it, returning the server-side
1448    /// stream and a split client-side IceSocketWrapper::TcpStream.
1449    async fn tcp_loopback_pair() -> (tokio::net::TcpStream, IceSocketWrapper) {
1450        let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
1451        let server_addr = listener.local_addr().unwrap();
1452
1453        let client_stream = tokio::net::TcpStream::connect(server_addr).await.unwrap();
1454        let (server_stream, _) = listener.accept().await.unwrap();
1455
1456        let wrapper = {
1457            let (read, write) = client_stream.into_split();
1458            IceSocketWrapper::TcpStream(
1459                Arc::new(tokio::sync::Mutex::new(read)),
1460                Arc::new(tokio::sync::Mutex::new(write)),
1461                server_addr,
1462            )
1463        };
1464
1465        (server_stream, wrapper)
1466    }
1467
1468    /// RFC 4571 framing round-trip: write framed data via IceSocketWrapper::TcpStream
1469    /// and read it back on the raw server-side TCP socket.
1470    #[tokio::test]
1471    async fn test_tcp_rfc4571_framing_roundtrip() {
1472        let (mut server_stream, wrapper) = tcp_loopback_pair().await;
1473
1474        let payload = b"hello rfc4571";
1475        let len = payload.len() as u16;
1476        let mut expected_frame = Vec::with_capacity(2 + payload.len());
1477        expected_frame.extend_from_slice(&len.to_be_bytes());
1478        expected_frame.extend_from_slice(payload);
1479
1480        // Send via wrapper
1481        wrapper
1482            .send_to(payload, server_stream.local_addr().unwrap())
1483            .await
1484            .unwrap();
1485
1486        // Read framed data on server side
1487        let mut len_buf = [0u8; 2];
1488        let s = &mut server_stream;
1489        s.read_exact(&mut len_buf).await.unwrap();
1490        let frame_len = u16::from_be_bytes(len_buf) as usize;
1491        let mut frame = vec![0u8; frame_len];
1492        s.read_exact(&mut frame).await.unwrap();
1493        assert_eq!(&frame, payload);
1494    }
1495
1496    /// Send multiple messages via IceSocketWrapper::TcpStream; verify they arrive
1497    /// correctly as separate RFC 4571 frames.
1498    #[tokio::test]
1499    async fn test_tcp_rfc4571_multiple_writes() {
1500        let (mut server_stream, wrapper) = tcp_loopback_pair().await;
1501
1502        let msgs: &[&[u8]] = &[b"msg-a", b"msg-bb", b"msg-ccc"];
1503        for msg in msgs {
1504            wrapper
1505                .send_to(msg, server_stream.local_addr().unwrap())
1506                .await
1507                .unwrap();
1508        }
1509
1510        let s = &mut server_stream;
1511        for expected in msgs {
1512            let mut len_buf = [0u8; 2];
1513            s.read_exact(&mut len_buf).await.unwrap();
1514            let frame_len = u16::from_be_bytes(len_buf) as usize;
1515            let mut frame = vec![0u8; frame_len];
1516            s.read_exact(&mut frame).await.unwrap();
1517            assert_eq!(&frame, expected);
1518        }
1519    }
1520
1521    /// recv_from on IceSocketWrapper::TcpStream: write a valid RFC 4571 frame
1522    /// to the server stream, then read it back via the wrapper.
1523    #[tokio::test]
1524    async fn test_tcp_recv_from_rfc4571() {
1525        let (mut server_stream, wrapper) = tcp_loopback_pair().await;
1526        let server_addr = server_stream.local_addr().unwrap();
1527
1528        let payload = b"recv-test-payload";
1529        let len = payload.len() as u16;
1530        let mut frame = Vec::with_capacity(2 + payload.len());
1531        frame.extend_from_slice(&len.to_be_bytes());
1532        frame.extend_from_slice(payload);
1533
1534        // Write from server side
1535        server_stream.writable().await.unwrap();
1536        let s = &mut server_stream;
1537        s.write_all(&frame).await.unwrap();
1538
1539        // Read via wrapper recv_from
1540        let mut buf = [0u8; 2048];
1541        let (n, addr) = wrapper.recv_from(&mut buf).await.unwrap();
1542        assert_eq!(&buf[..n], payload);
1543        assert_eq!(addr, server_addr);
1544    }
1545
1546    /// send_dtls_record_batch over TCP sends RFC 4571-framed records in a single
1547    /// syscall; verify all records arrive correctly on the server side.
1548    #[tokio::test]
1549    async fn test_send_dtls_record_batch_tcp() {
1550        let (mut server_stream, wrapper) = tcp_loopback_pair().await;
1551        let server_addr = server_stream.local_addr().unwrap();
1552
1553        let (socket_tx, socket_rx) = watch::channel(Some(wrapper));
1554        let conn = IceConn::new(socket_rx, server_addr, Some("test-tcp".into()));
1555        drop(socket_tx);
1556
1557        let records: Vec<Vec<u8>> = vec![
1558            b"dtls-record-1".to_vec(),
1559            b"dtls-record-22".to_vec(),
1560            b"dtls-record-333".to_vec(),
1561        ];
1562
1563        conn.send_dtls_record_batch(&records).await.unwrap();
1564
1565        let s = &mut server_stream;
1566        for expected in &records {
1567            let mut len_buf = [0u8; 2];
1568            s.read_exact(&mut len_buf).await.unwrap();
1569            let frame_len = u16::from_be_bytes(len_buf) as usize;
1570            let mut frame = vec![0u8; frame_len];
1571            s.read_exact(&mut frame).await.unwrap();
1572            assert_eq!(&frame, expected.as_slice());
1573        }
1574    }
1575
1576    /// send_dtls_record_batch on a UDP socket should fall through to individual
1577    /// send calls.  Verify all records arrive.
1578    #[tokio::test]
1579    async fn test_send_dtls_record_batch_udp_fallback() {
1580        let udp = Arc::new(tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap());
1581        let wrapper = IceSocketWrapper::Udp(udp.clone());
1582        let (socket_tx, socket_rx) = watch::channel(Some(wrapper));
1583
1584        let receiver = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
1585        let receiver_addr = receiver.local_addr().unwrap();
1586
1587        let conn = IceConn::new(socket_rx, receiver_addr, None);
1588        drop(socket_tx);
1589
1590        let records: Vec<Vec<u8>> = vec![b"rec-a".to_vec(), b"rec-b".to_vec()];
1591        conn.send_dtls_record_batch(&records).await.unwrap();
1592
1593        // Read all datagrams (may arrive as separate UDP packets)
1594        let mut buf = [0u8; 2048];
1595        for expected in &records {
1596            let (len, _) = receiver.recv_from(&mut buf).await.unwrap();
1597            assert_eq!(&buf[..len], expected.as_slice());
1598        }
1599    }
1600}