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