Skip to main content

rtc/peer_connection/
mod.rs

1//! Peer-to-peer connections
2//!
3//! This module implements the `RTCPeerConnection` interface as defined in the
4//! [W3C WebRTC specification](https://www.w3.org/TR/webrtc/). It provides
5//! the core functionality for establishing peer-to-peer connections, negotiating
6//! media capabilities, and managing data channels.
7//!
8//! # Overview
9//!
10//! `RTCPeerConnection` is the central interface in WebRTC. It handles:
11//!
12//! - **Signaling**: Creating and exchanging SDP offers/answers
13//! - **ICE**: Gathering candidates and establishing connectivity
14//! - **Media**: Managing audio/video tracks and transceivers
15//! - **Data**: Creating and managing data channels
16//! - **Security**: DTLS encryption for all communication
17//!
18//! # Architecture
19//!
20//! This is a **sans-I/O** implementation, meaning it separates protocol logic
21//! from I/O operations. The application is responsible for:
22//!
23//! - Transmitting/receiving network packets
24//! - Managing the event loop
25//! - Handling signaling channel communication
26//!
27//! ## Sans-I/O Benefits
28//!
29//! - **Flexibility**: Works with any I/O runtime (tokio, async-std, blocking, etc.)
30//! - **Testability**: Protocol logic can be tested without network I/O
31//! - **Control**: Application has full control over threading and scheduling
32//!
33//! # Connection Establishment
34//!
35//! The typical WebRTC connection flow:
36//!
37//! ```text
38//! Peer A (Offerer)              Signaling Server              Peer B (Answerer)
39//! ════════════════              ════════════════              ═══════════════════
40//!      │                               │                               │
41//!      │ 1. create_offer()             │                               │
42//!      │─────────────────┐             │                               │
43//!      │                 │             │                               │
44//!      │<────────────────┘             │                               │
45//!      │                               │                               │
46//!      │ 2. set_local_description()    │                               │
47//!      │─────────────────┐             │                               │
48//!      │                 │             │                               │
49//!      │<────────────────┘             │                               │
50//!      │                               │                               │
51//!      │ 3. send offer (via signaling) │                               │
52//!      │──────────────────────────────>│──────────────────────────────>│
53//!      │                               │                               │
54//!      │                               │  4. set_remote_description()  │
55//!      │                               │                  ┌────────────┤
56//!      │                               │                  │            │
57//!      │                               │                  └───────────>│
58//!      │                               │                               │
59//!      │                               │       5. create_answer()      │
60//!      │                               │                  ┌────────────┤
61//!      │                               │                  │            │
62//!      │                               │                  └───────────>│
63//!      │                               │                               │
64//!      │                               │  6. set_local_description()   │
65//!      │                               │                  ┌────────────┤
66//!      │                               │                  │            │
67//!      │                               │                  └───────────>│
68//!      │                               │                               │
69//!      │ 7. receive answer             │<──────────────────────────────│
70//!      │<──────────────────────────────┤                               │
71//!      │                               │                               │
72//!      │ 8. set_remote_description()   │                               │
73//!      │─────────────────┐             │                               │
74//!      │                 │             │                               │
75//!      │<────────────────┘             │                               │
76//!      │                               │                               │
77//!      │ 9. ICE candidates exchanged   │                               │
78//!      │<─────────────────────────────────────────────────────────────>│
79//!      │                               │                               │
80//!      │ 10. Media/data flows directly │                               │
81//!      │<═════════════════════════════════════════════════════════════>│
82//! ```
83//!
84//! # Examples
85//!
86//! ## Creating a Peer Connection
87//!
88//! ```
89//! # use std::time::Instant;
90//! use rtc::peer_connection::RTCPeerConnectionBuilder;
91//!
92//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
93//! // Create with default configuration
94//! let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
95//! # Ok(())
96//! # }
97//! ```
98//!
99//! ## Creating an Offer (Initiating Peer)
100//!
101//! ```no_run
102//! # use std::time::Instant;
103//! use rtc::peer_connection::RTCPeerConnectionBuilder;
104//!
105//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
106//! let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
107//!
108//! // Add media track or data channel first
109//! // pc.add_track(audio_track)?;
110//!
111//! // Create offer
112//! let offer = pc.create_offer(None)?;
113//!
114//! // Set as local description
115//! pc.set_local_description(Instant::now(), offer.clone())?;
116//!
117//! // Send offer.sdp to remote peer via signaling channel
118//! // signaling_channel.send(offer.sdp)?;
119//! # Ok(())
120//! # }
121//! ```
122//!
123//! ## Answering an Offer (Responding Peer)
124//!
125//! ```no_run
126//! # use std::time::Instant;
127//! use rtc::peer_connection::RTCPeerConnectionBuilder;
128//! use rtc::peer_connection::sdp::RTCSessionDescription;
129//!
130//! # fn example(remote_offer_sdp: String) -> Result<(), Box<dyn std::error::Error>> {
131//! let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
132//!
133//! // Receive offer from remote peer
134//! let offer = RTCSessionDescription::offer(remote_offer_sdp)?;
135//!
136//! // Set as remote description
137//! pc.set_remote_description(Instant::now(), offer)?;
138//!
139//! // Create answer
140//! let answer = pc.create_answer(None)?;
141//!
142//! // Set as local description
143//! pc.set_local_description(Instant::now(), answer.clone())?;
144//!
145//! // Send answer.sdp to remote peer via signaling channel
146//! // signaling_channel.send(answer.sdp)?;
147//! # Ok(())
148//! # }
149//! ```
150//!
151//! ## Adding Media Tracks
152//!
153//! ```no_run
154//! # use std::time::Instant;
155//! use rtc::peer_connection::RTCPeerConnectionBuilder;
156//! use rtc::media_stream::MediaStreamTrack;
157//! use rtc::rtp_transceiver::rtp_sender::RtpCodecKind;
158//!
159//! # fn example(audio_track: MediaStreamTrack) -> Result<(), Box<dyn std::error::Error>> {
160//! let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
161//!
162//! // Add an audio track
163//! let sender_id = pc.add_track(audio_track)?;
164//!
165//! // Or add a transceiver for receiving
166//! let transceiver_id = pc.add_transceiver_from_kind(RtpCodecKind::Video, None)?;
167//! # Ok(())
168//! # }
169//! ```
170//!
171//! ## Creating Data Channels
172//!
173//! ```no_run
174//! # use std::time::Instant;
175//! use rtc::peer_connection::RTCPeerConnectionBuilder;
176//! use rtc::data_channel::RTCDataChannelInit;
177//!
178//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
179//! let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
180//!
181//! // Create a reliable, ordered data channel
182//! let init = RTCDataChannelInit {
183//!     ordered: true,
184//!     max_retransmits: None,
185//!     ..Default::default()
186//! };
187//!
188//! let channel_id = pc.create_data_channel("my-channel", Some(init))?;
189//! # Ok(())
190//! # }
191//! ```
192//!
193//! ## ICE Candidate Exchange
194//!
195//! ```no_run
196//! use rtc::peer_connection::{RTCPeerConnection, RTCPeerConnectionBuilder};
197//! use rtc::peer_connection::transport::RTCIceCandidateInit;
198//!
199//! # fn example(mut pc: RTCPeerConnection) -> Result<(), Box<dyn std::error::Error>> {
200//! // When local candidates are gathered, send to remote peer
201//! // (In sans-I/O, you'd poll for events to get candidates)
202//!
203//! // When receiving remote candidate from signaling channel
204//! let remote_candidate = RTCIceCandidateInit {
205//!     candidate: "candidate:1 1 UDP 2130706431 192.168.1.100 54321 typ host".to_string(),
206//!     ..Default::default()
207//! };
208//!
209//! pc.add_remote_candidate(remote_candidate)?;
210//! # Ok(())
211//! # }
212//! ```
213//!
214//! # State Management
215//!
216//! The peer connection maintains several state machines:
217//!
218//! - **Signaling State**: SDP negotiation progress (stable, have-local-offer, etc.)
219//! - **ICE Connection State**: Network connectivity status
220//! - **ICE Gathering State**: Candidate gathering progress
221//! - **Connection State**: Overall connection health
222//!
223//! Monitor these states through the event system (sans-I/O polling).
224//!
225//! # Thread Safety
226//!
227//! `RTCPeerConnection` is **not** thread-safe. The application must ensure
228//! exclusive access or use appropriate synchronization primitives.
229//!
230//! # Specification
231//!
232//! - [W3C WebRTC 1.0] - Main specification
233//! - [RFC 9429] - JSEP: JavaScript Session Establishment Protocol
234//! - [RFC 8866] - SDP: Session Description Protocol
235//! - [RFC 8445] - ICE: Interactive Connectivity Establishment
236//! - [RFC 8831] - WebRTC Data Channels
237//!
238//! [W3C WebRTC 1.0]: https://www.w3.org/TR/webrtc/
239//! [RFC 9429]: https://datatracker.ietf.org/doc/html/rfc9429
240//! [RFC 8866]: https://datatracker.ietf.org/doc/html/rfc8866
241//! [RFC 8445]: https://datatracker.ietf.org/doc/html/rfc8445
242//! [RFC 8831]: https://datatracker.ietf.org/doc/html/rfc8831
243
244pub mod certificate;
245pub mod configuration;
246pub mod event;
247pub(crate) mod handler;
248mod internal;
249pub mod message;
250pub mod sdp;
251pub mod state;
252pub mod transport;
253
254use crate::data_channel::init::RTCDataChannelInit;
255use crate::data_channel::parameters::DataChannelParameters;
256use crate::data_channel::registry::DataChannelRegistry;
257use crate::data_channel::state::RTCDataChannelState;
258use crate::data_channel::{RTCDataChannel, RTCDataChannelId, internal::RTCDataChannelInternal};
259use crate::media_stream::track::MediaStreamTrack;
260use crate::peer_connection::configuration::media_engine::MediaEngine;
261use crate::peer_connection::configuration::setting_engine::{SctpMaxMessageSize, SettingEngine};
262use crate::peer_connection::configuration::{
263    RTCConfiguration, RTCIceTransportPolicy,
264    offer_answer_options::{RTCAnswerOptions, RTCOfferOptions},
265};
266use crate::peer_connection::event::RTCPeerConnectionEvent;
267use crate::peer_connection::handler::PipelineContext;
268use crate::peer_connection::handler::dtls::DtlsHandlerContext;
269use crate::peer_connection::handler::ice::IceHandlerContext;
270use crate::peer_connection::handler::sctp::SctpHandlerContext;
271use crate::peer_connection::sdp::MediaDescriptionExt;
272use crate::peer_connection::sdp::session_description::RTCSessionDescription;
273use crate::peer_connection::sdp::{
274    extract_fingerprint, extract_ice_details, get_application_media,
275    get_application_media_section_max_message_size, get_application_media_section_sctp_port,
276    get_mid_value, get_peer_direction, has_ice_trickle_option, is_lite_set, sdp_type::RTCSdpType,
277    update_sdp_origin,
278};
279use crate::peer_connection::state::RTCIceGatheringState;
280use crate::peer_connection::state::ice_connection_state::RTCIceConnectionState;
281use crate::peer_connection::state::peer_connection_state::{
282    NegotiationNeededState, RTCPeerConnectionState,
283};
284use crate::peer_connection::state::signaling_state::{RTCSignalingState, StateChangeOp};
285use crate::peer_connection::transport::RTCSctpTransport;
286use crate::peer_connection::transport::dtls::fingerprint::RTCDtlsFingerprint;
287use crate::peer_connection::transport::dtls::parameters::RTCDtlsParameters;
288use crate::peer_connection::transport::dtls::role::{
289    DEFAULT_DTLS_ROLE_ANSWER, DEFAULT_DTLS_ROLE_OFFER, RTCDtlsRole,
290};
291use crate::peer_connection::transport::dtls::{DtlsTransport, RTCDtlsTransportConfig};
292use crate::peer_connection::transport::ice::IceTransport;
293use crate::peer_connection::transport::ice::candidate::RTCIceCandidateInit;
294use crate::peer_connection::transport::ice::parameters::RTCIceParameters;
295use crate::peer_connection::transport::ice::role::RTCIceRole;
296use crate::peer_connection::transport::sctp::SctpTransport;
297use crate::peer_connection::transport::sctp::capabilities::SCTPTransportCapabilities;
298use crate::rtp_transceiver::direction::RTCRtpTransceiverDirection;
299use crate::rtp_transceiver::rtp_receiver::RTCRtpReceiver;
300use crate::rtp_transceiver::rtp_sender::RTCRtpCodecParameters;
301use crate::rtp_transceiver::rtp_sender::RTCRtpSender;
302use crate::rtp_transceiver::rtp_sender::internal::RTCRtpSenderInternal;
303use crate::rtp_transceiver::rtp_sender::rtp_codec::{
304    CodecMatch, RtpCodecKind, codec_parameters_fuzzy_search,
305};
306use crate::rtp_transceiver::{
307    RTCRtpReceiverId, RTCRtpSenderId, RTCRtpTransceiver, RTCRtpTransceiverId,
308    RTCRtpTransceiverInit, internal::RTCRtpTransceiverInternal,
309};
310use crate::statistics::StatsSelector;
311use crate::statistics::accumulator::RTCStatsAccumulator;
312use crate::statistics::report::RTCStatsReport;
313use ::sdp::description::session::Origin;
314use ::sdp::util::ConnectionRole;
315use ice::AgentConfig;
316use ice::candidate::{Candidate, unmarshal_candidate};
317use interceptor::{Interceptor, Registry};
318use shared::error::{Error, Result};
319use shared::util::math_rand_alpha;
320use std::time::Instant;
321
322/// Builder for creating RTCPeerConnection instances.
323///
324/// This builder provides a fluent API for configuring peer connections with:
325/// - ICE servers (STUN/TURN) via [`RTCConfiguration`]
326/// - Media codecs and RTP extensions via [`MediaEngine`]
327/// - Low-level transport settings via [`SettingEngine`]
328/// - RTP/RTCP interceptors for NACK, TWCC, and RTCP reports
329///
330/// # Examples
331///
332/// ## Basic peer connection
333///
334/// ```
335/// # use std::time::Instant;
336/// use rtc::peer_connection::RTCPeerConnectionBuilder;
337///
338/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
339/// let pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
340/// # Ok(())
341/// # }
342/// ```
343///
344/// ## With ICE servers
345///
346/// ```
347/// # use std::time::Instant;
348/// use rtc::peer_connection::RTCPeerConnectionBuilder;
349/// use rtc::peer_connection::configuration::{RTCConfigurationBuilder, RTCIceServer};
350///
351/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
352/// let pc = RTCPeerConnectionBuilder::new()
353///     .with_configuration(
354///         RTCConfigurationBuilder::new()
355///             .with_ice_servers(vec![RTCIceServer {
356///                 urls: vec!["stun:stun.l.google.com:19302".to_string()],
357///                 ..Default::default()
358///             }])
359///             .build()
360///     )
361///     .build(Instant::now())?;
362/// # Ok(())
363/// # }
364/// ```
365///
366/// ## With custom media engine
367///
368/// ```
369/// # use std::time::Instant;
370/// use rtc::peer_connection::RTCPeerConnectionBuilder;
371/// use rtc::peer_connection::configuration::media_engine::MediaEngine;
372///
373/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
374/// let mut media_engine = MediaEngine::default();
375/// media_engine.register_default_codecs()?;
376///
377/// let pc = RTCPeerConnectionBuilder::new()
378///     .with_media_engine(media_engine)
379///     .build(Instant::now())?;
380/// # Ok(())
381/// # }
382/// ```
383///
384/// ## With interceptors
385///
386/// ```
387/// # use std::time::Instant;
388/// use rtc::peer_connection::RTCPeerConnectionBuilder;
389/// use rtc::peer_connection::configuration::media_engine::MediaEngine;
390/// use rtc::peer_connection::configuration::interceptor_registry::{
391///     Registry, register_default_interceptors,
392/// };
393///
394/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
395/// let mut media_engine = MediaEngine::default();
396/// let registry = Registry::new();
397/// let registry = register_default_interceptors(registry, &mut media_engine)?;
398///
399/// let pc = RTCPeerConnectionBuilder::new()
400///     .with_media_engine(media_engine)
401///     .with_interceptor_registry(registry)
402///     .build(Instant::now())?;
403/// # Ok(())
404/// # }
405/// ```
406#[derive(Default)]
407pub struct RTCPeerConnectionBuilder {
408    configuration: RTCConfiguration,
409    media_engine: MediaEngine,
410    setting_engine: SettingEngine,
411    interceptor_registry: Registry,
412}
413
414impl RTCPeerConnectionBuilder {
415    /// Creates a new RTCPeerConnectionBuilder with default configuration.
416    ///
417    /// The default builder includes:
418    /// - Empty ICE server list
419    /// - Default MediaEngine (no codecs registered)
420    /// - Default SettingEngine (standard timeouts and limits)
421    /// - NoopInterceptor (no RTP/RTCP processing)
422    ///
423    /// Use `with_*` methods to customize configuration before calling `build()`.
424    ///
425    /// # Examples
426    ///
427    /// ```
428    /// # use std::time::Instant;
429    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
430    ///
431    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
432    /// let pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
433    /// # Ok(())
434    /// # }
435    /// ```
436    pub fn new() -> Self {
437        Self::default()
438    }
439}
440
441impl RTCPeerConnectionBuilder {
442    /// Sets the RTCConfiguration for the peer connection.
443    ///
444    /// The configuration includes ICE servers, transport policies, bundle policies,
445    /// RTCP mux policies, and certificates.
446    ///
447    /// # Examples
448    ///
449    /// ```
450    /// # use std::time::Instant;
451    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
452    /// use rtc::peer_connection::configuration::{RTCConfigurationBuilder, RTCIceServer};
453    ///
454    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
455    /// let config = RTCConfigurationBuilder::new()
456    ///     .with_ice_servers(vec![RTCIceServer {
457    ///         urls: vec!["stun:stun.l.google.com:19302".to_string()],
458    ///         ..Default::default()
459    ///     }])
460    ///     .build();
461    ///
462    /// let pc = RTCPeerConnectionBuilder::new()
463    ///     .with_configuration(config)
464    ///     .build(Instant::now())?;
465    /// # Ok(())
466    /// # }
467    /// ```
468    pub fn with_configuration(mut self, configuration: RTCConfiguration) -> Self {
469        self.configuration = configuration;
470        self
471    }
472
473    /// Sets the MediaEngine for the peer connection.
474    ///
475    /// The media engine configures codecs and RTP header extensions.
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// # use std::time::Instant;
481    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
482    /// use rtc::peer_connection::configuration::media_engine::MediaEngine;
483    ///
484    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
485    /// let mut media_engine = MediaEngine::default();
486    /// media_engine.register_default_codecs()?;
487    ///
488    /// let pc = RTCPeerConnectionBuilder::new()
489    ///     .with_media_engine(media_engine)
490    ///     .build(Instant::now())?;
491    /// # Ok(())
492    /// # }
493    /// ```
494    pub fn with_media_engine(mut self, media_engine: MediaEngine) -> Self {
495        self.media_engine = media_engine;
496        self
497    }
498
499    /// Sets the SettingEngine for the peer connection.
500    ///
501    /// The setting engine configures low-level transport parameters including
502    /// timeouts, buffer sizes, ICE settings, and SCTP parameters.
503    ///
504    /// # Examples
505    ///
506    /// ```
507    /// # use std::time::Instant;
508    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
509    /// use rtc::peer_connection::configuration::setting_engine::SettingEngineBuilder;
510    /// use std::time::Duration;
511    ///
512    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
513    /// let setting_engine = SettingEngineBuilder::new()
514    ///     .with_ice_timeouts(
515    ///         Some(Duration::from_secs(30)),
516    ///         Some(Duration::from_secs(60)),
517    ///         Some(Duration::from_millis(100)),
518    ///     )
519    ///     .build();
520    ///
521    /// let pc = RTCPeerConnectionBuilder::new()
522    ///     .with_setting_engine(setting_engine)
523    ///     .build(Instant::now())?;
524    /// # Ok(())
525    /// # }
526    /// ```
527    pub fn with_setting_engine(mut self, setting_engine: SettingEngine) -> Self {
528        self.setting_engine = setting_engine;
529        self
530    }
531
532    /// Configures the peer connection with an interceptor registry.
533    ///
534    /// Interceptors process RTP/RTCP packets as they flow through the pipeline,
535    /// enabling features like:
536    /// - NACK (Negative Acknowledgment) for packet loss recovery
537    /// - TWCC (Transport-Wide Congestion Control) for bandwidth estimation
538    /// - RTCP Reports for quality statistics
539    ///
540    /// This method replaces the builder's interceptor type — `NoopInterceptor` by default —
541    /// with the registry's, so it returns a `RTCPeerConnectionBuilder<P>` rather than
542    /// `Self`. Every other builder setting is carried over, and the remaining setters are
543    /// available on the returned builder, so this does not have to be the last call before
544    /// `build()`; it is simply the only one that changes the builder's type.
545    ///
546    /// # Type Parameters
547    ///
548    /// * `P` - The interceptor type produced by the registry
549    ///
550    /// # Examples
551    ///
552    /// ```
553    /// # use std::time::Instant;
554    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
555    /// use rtc::peer_connection::configuration::media_engine::MediaEngine;
556    /// use rtc::peer_connection::configuration::interceptor_registry::{
557    ///     Registry, register_default_interceptors,
558    /// };
559    ///
560    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
561    /// let mut media_engine = MediaEngine::default();
562    /// let registry = Registry::new();
563    /// let registry = register_default_interceptors(registry, &mut media_engine)?;
564    ///
565    /// let pc = RTCPeerConnectionBuilder::new()
566    ///     .with_media_engine(media_engine)
567    ///     .with_interceptor_registry(registry)
568    ///     .build(Instant::now())?;
569    /// # Ok(())
570    /// # }
571    /// ```
572    ///
573    /// # One connection type, whatever the chain
574    ///
575    /// A chain is a flat list of interceptors, so the connection has one concrete type whatever
576    /// the list contains. That is what lets a non-generic struct own one, or two connections with
577    /// *different* chains share a collection:
578    ///
579    /// ```
580    /// # use std::time::Instant;
581    /// use rtc::peer_connection::configuration::interceptor_registry::{
582    ///     Registry, register_default_interceptors,
583    /// };
584    /// use rtc::peer_connection::configuration::media_engine::MediaEngine;
585    /// use rtc::peer_connection::{RTCPeerConnection, RTCPeerConnectionBuilder};
586    ///
587    /// struct Session {
588    ///     peer_connection: RTCPeerConnection, // no type parameter
589    /// }
590    ///
591    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
592    /// let mut media_engine = MediaEngine::default();
593    /// let registry =
594    ///     register_default_interceptors(Registry::new(), &mut media_engine)?;
595    ///
596    /// let session = Session {
597    ///     peer_connection: RTCPeerConnectionBuilder::new()
598    ///         .with_media_engine(media_engine)
599    ///         .with_interceptor_registry(registry)
600    ///         .build(Instant::now())?,
601    /// };
602    /// # Ok(())
603    /// # }
604    /// ```
605    ///
606    /// The registry is assembled into the chain by [`build`](RTCPeerConnectionBuilder::build),
607    /// so it stays a list — inspectable, extendable — right up until the connection is made.
608    pub fn with_interceptor_registry(mut self, interceptor_registry: Registry) -> Self {
609        self.interceptor_registry = interceptor_registry;
610        self
611    }
612
613    /// Builds the RTCPeerConnection with the configured settings.
614    ///
615    /// This method validates the configuration and creates a new peer connection.
616    /// If validation fails (e.g., expired certificates, invalid ICE servers),
617    /// an error is returned.
618    ///
619    /// # Errors
620    ///
621    /// Returns an error if:
622    /// - Certificates have expired
623    /// - ICE server URLs are invalid
624    /// - Other validation checks fail
625    ///
626    /// # Examples
627    ///
628    /// ```
629    /// # use std::time::Instant;
630    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
631    ///
632    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
633    /// let pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
634    /// # Ok(())
635    /// # }
636    /// ```
637    pub fn build(self, now: Instant) -> Result<RTCPeerConnection> {
638        RTCPeerConnection::new(
639            now,
640            self.configuration,
641            self.media_engine,
642            self.setting_engine,
643            // The registry is a list until here; `build` freezes it into the chain.
644            Box::new(self.interceptor_registry.build()),
645        )
646    }
647}
648
649/// The `RTCPeerConnection` interface represents a WebRTC connection between the local computer
650/// and a remote peer. It provides methods to connect to a remote peer, maintain and monitor
651/// the connection, and close the connection once it's no longer needed.
652///
653/// This is a sans-I/O implementation following the [W3C WebRTC specification](https://www.w3.org/TR/webrtc/).
654///
655/// # Driving the connection
656///
657/// This type performs **no I/O and reads no clock**. It never opens a socket, never sleeps, and
658/// never calls `Instant::now()` on your behalf: every method that needs the time takes it as an
659/// argument. Progress happens only when you feed it something. That is what makes a whole
660/// session reproducible in a test with no socket and no sleep.
661///
662/// The driving methods come from the [`sansio::Protocol`] trait, so **the trait must be in scope**
663/// (`use rtc::sansio::Protocol;`) or none of them will resolve:
664///
665/// | Method | Direction | What it does |
666/// | --- | --- | --- |
667/// | [`handle_read`] | in | Feed one received datagram, tagged with its 5-tuple and arrival instant |
668/// | [`poll_write`] | out | Take the next packet to put on the wire; drain until `None` |
669/// | [`poll_read`] | out | Take the next inbound RTP/RTCP/data-channel message for the application |
670/// | [`poll_event`] | out | Take the next state change or notification |
671/// | [`poll_timeout`] | out | Next deadline for retransmissions, keepalives and ICE checks |
672/// | [`handle_timeout`] | in | Report that a deadline has passed |
673/// | [`handle_write`] | in | Queue an outbound RTP/RTCP/data-channel message |
674/// | [`close`] | in | Shut the connection down |
675///
676/// Nothing is sent eagerly: `handle_read`, `handle_timeout`, `handle_write` and the negotiation
677/// methods all *queue* packets, so drain `poll_write` after any of them.
678///
679/// [`handle_event`] also exists on the trait, but [`RTCEvent`] is uninhabited — no value of it can
680/// be constructed, so there is nothing to call it with. It reserves the signature for the first
681/// inbound event variant.
682///
683/// # Back-pressure
684///
685/// [`poll_read`] is the throttle. Undrained data-channel messages leave bytes in SCTP's reassembly
686/// queue, which lowers the receiver-window credit advertised in every SACK, which tells the peer to
687/// slow down. **Declining to call `poll_read` is how back-pressure is applied**; resume when the
688/// application catches up. Media is never throttled this way: RTP arrives over SRTP and is subject
689/// to none of SCTP's flow control, and `poll_read` interleaves the two by the instant each packet
690/// was observed, so a stalled data channel cannot starve video.
691///
692/// # ICE candidates
693///
694/// Because there is no I/O here, there is no candidate gathering either: the application owns the
695/// sockets, so it owns gathering. Hand every local candidate to [`Self::add_local_candidate`], and
696/// finish with an empty candidate string to signal end-of-gathering — see that method for the
697/// details.
698///
699/// # Examples
700///
701/// ```
702/// # use std::time::Instant;
703/// use rtc::peer_connection::RTCPeerConnectionBuilder;
704///
705/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
706/// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
707/// # Ok(())
708/// # }
709/// ```
710///
711/// A complete event loop is shown in the [crate-level documentation](crate).
712///
713/// [`sansio::Protocol`]: sansio::Protocol
714/// [`handle_read`]: sansio::Protocol::handle_read
715/// [`poll_write`]: sansio::Protocol::poll_write
716/// [`poll_read`]: sansio::Protocol::poll_read
717/// [`poll_event`]: sansio::Protocol::poll_event
718/// [`poll_timeout`]: sansio::Protocol::poll_timeout
719/// [`handle_timeout`]: sansio::Protocol::handle_timeout
720/// [`handle_write`]: sansio::Protocol::handle_write
721/// [`handle_event`]: sansio::Protocol::handle_event
722/// [`close`]: sansio::Protocol::close
723/// [`RTCEvent`]: crate::peer_connection::event::RTCEvent
724pub struct RTCPeerConnection {
725    //////////////////////////////////////////////////
726    // PeerConnection WebRTC Spec Interface Definition
727    //////////////////////////////////////////////////
728    pub(crate) configuration: RTCConfiguration,
729    pub(crate) media_engine: MediaEngine,
730    pub(crate) setting_engine: SettingEngine,
731    pub(crate) interceptor: Box<dyn Interceptor>,
732
733    local_description: Option<RTCSessionDescription>,
734    current_local_description: Option<RTCSessionDescription>,
735    pending_local_description: Option<RTCSessionDescription>,
736    remote_description: Option<RTCSessionDescription>,
737    current_remote_description: Option<RTCSessionDescription>,
738    pending_remote_description: Option<RTCSessionDescription>,
739
740    pub(crate) signaling_state: RTCSignalingState,
741    pub(crate) peer_connection_state: RTCPeerConnectionState,
742    can_trickle_ice_candidates: Option<bool>,
743
744    //////////////////////////////////////////////////
745    // PeerConnection Internal State Machine
746    //////////////////////////////////////////////////
747    pub(crate) pipeline_context: PipelineContext,
748    pub(crate) data_channels: DataChannelRegistry,
749    pub(super) rtp_transceivers: Vec<RTCRtpTransceiverInternal>,
750
751    greater_mid: isize,
752    sdp_origin: Origin,
753    last_offer: String,
754    last_answer: String,
755
756    ice_restart_requested: Option<RTCOfferOptions>,
757    negotiation_needed_state: NegotiationNeededState,
758    is_negotiation_ongoing: bool,
759}
760
761impl RTCPeerConnection {
762    /// Creates an SDP offer to start a new WebRTC connection to a remote peer.
763    ///
764    /// The offer includes information about the attached media tracks, codecs and options supported
765    /// by the browser, and ICE candidates gathered by the ICE agent. This offer can be sent to a
766    /// remote peer over a signaling channel to establish a connection.
767    ///
768    /// # Parameters
769    ///
770    /// * `options` - Optional configuration for the offer, such as whether to restart ICE.
771    ///
772    /// # Returns
773    ///
774    /// Returns an `RTCSessionDescription` containing the SDP offer.
775    ///
776    /// # Errors
777    ///
778    /// Returns an error if:
779    /// - The peer connection is closed
780    /// - There's an error generating the SDP
781    ///
782    /// # Specification
783    ///
784    /// See [createOffer](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-createoffer)
785    pub fn create_offer(
786        &mut self,
787        mut options: Option<RTCOfferOptions>,
788    ) -> Result<RTCSessionDescription> {
789        if self.peer_connection_state == RTCPeerConnectionState::Closed {
790            return Err(Error::ErrConnectionClosed);
791        }
792
793        // Staging, not restarting: the offer must advertise fresh ICE credentials, but JSEP
794        // requires `createOffer` to be free of side effects, and inbound STUN is still being
795        // validated against the current ufrag/pwd. `set_local_description` applies the restart.
796        let is_ice_restart_requested = self
797            .ice_restart_requested
798            .take()
799            .is_some_and(|options| options.ice_restart)
800            || options.take().is_some_and(|options| options.ice_restart);
801
802        if is_ice_restart_requested {
803            self.stage_ice_restart()?;
804        }
805
806        // include unmatched local transceivers
807        // update the greater mid if the remote description provides a greater one
808        if let Some(d) = self.current_remote_description.as_ref()
809            && let Some(parsed) = &d.parsed
810        {
811            for media in &parsed.media_descriptions {
812                if let Some(mid) = get_mid_value(media) {
813                    if mid.is_empty() {
814                        continue;
815                    }
816                    let numeric_mid = match mid.parse::<isize>() {
817                        Ok(n) => n,
818                        Err(_) => continue,
819                    };
820                    if numeric_mid > self.greater_mid {
821                        self.greater_mid = numeric_mid;
822                    }
823                }
824            }
825        }
826        for transceiver in &mut self.rtp_transceivers {
827            if let Some(mid) = transceiver.mid()
828                && !mid.is_empty()
829            {
830                if let Ok(numeric_mid) = mid.parse::<isize>()
831                    && numeric_mid > self.greater_mid
832                {
833                    self.greater_mid = numeric_mid;
834                }
835            } else {
836                self.greater_mid += 1;
837                transceiver.set_mid(format!("{}", self.greater_mid))?;
838            }
839        }
840
841        let mut d = if self.current_remote_description.is_none() {
842            self.generate_unmatched_sdp()?
843        } else {
844            self.generate_matched_sdp(
845                true, /*includeUnmatched */
846                DEFAULT_DTLS_ROLE_OFFER.to_connection_role(),
847                false,
848            )?
849        };
850
851        update_sdp_origin(&mut self.sdp_origin, &mut d);
852
853        let sdp = d.marshal();
854
855        let offer = RTCSessionDescription {
856            sdp_type: RTCSdpType::Offer,
857            sdp,
858            parsed: Some(d),
859        };
860
861        self.last_offer.clone_from(&offer.sdp);
862
863        Ok(offer)
864    }
865
866    /// Creates an SDP answer in response to an offer from a remote peer.
867    ///
868    /// This method must be called after `set_remote_description()` has been called
869    /// with an offer. The answer describes which media formats and codecs this peer
870    /// will accept and how the connection will be established.
871    ///
872    /// # Parameters
873    ///
874    /// - `options`: Optional answer configuration. Currently not used but reserved
875    ///   for future extensions.
876    ///
877    /// # Returns
878    ///
879    /// Returns an `RTCSessionDescription` containing the SDP answer that should be
880    /// set as the local description and sent to the remote peer.
881    ///
882    /// # Errors
883    ///
884    /// Returns an error if:
885    /// - No remote description has been set (`ErrNoRemoteDescription`)
886    /// - The peer connection is closed (`ErrConnectionClosed`)
887    /// - The signaling state is incorrect (`ErrIncorrectSignalingState`)
888    /// - SDP generation fails
889    ///
890    /// # Signaling State Requirements
891    ///
892    /// This method can only be called when the signaling state is:
893    /// - `HaveRemoteOffer` - After receiving an initial offer
894    /// - `HaveLocalPranswer` - After sending a provisional answer
895    ///
896    /// # Examples
897    ///
898    /// ## Basic Answer Flow
899    ///
900    /// ```no_run
901    /// # use std::time::Instant;
902    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
903    /// use rtc::peer_connection::sdp::RTCSessionDescription;
904    ///
905    /// # fn example(remote_offer_sdp: String) -> Result<(), Box<dyn std::error::Error>> {
906    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
907    ///
908    /// // 1. Receive and set remote offer
909    /// let offer = RTCSessionDescription::offer(remote_offer_sdp)?;
910    /// pc.set_remote_description(Instant::now(), offer)?;
911    ///
912    /// // 2. Create answer
913    /// let answer = pc.create_answer(None)?;
914    ///
915    /// // 3. Set as local description
916    /// pc.set_local_description(Instant::now(), answer.clone())?;
917    ///
918    /// // 4. Send answer to remote peer
919    /// // signaling_channel.send(answer.sdp)?;
920    /// # Ok(())
921    /// # }
922    /// ```
923    ///
924    /// ## With Media Tracks
925    ///
926    /// ```no_run
927    /// # use std::time::Instant;
928    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
929    /// use rtc::peer_connection::sdp::RTCSessionDescription;
930    /// use rtc::media_stream::MediaStreamTrack;
931    ///
932    /// # fn example(
933    /// #     remote_offer_sdp: String,
934    /// #     audio_track: MediaStreamTrack,
935    /// # ) -> Result<(), Box<dyn std::error::Error>> {
936    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
937    ///
938    /// // Set remote offer
939    /// let offer = RTCSessionDescription::offer(remote_offer_sdp)?;
940    /// pc.set_remote_description(Instant::now(), offer)?;
941    ///
942    /// // Add local track before creating answer
943    /// pc.add_track(audio_track)?;
944    ///
945    /// // Create answer (will include the track)
946    /// let answer = pc.create_answer(None)?;
947    /// pc.set_local_description(Instant::now(), answer)?;
948    /// # Ok(())
949    /// # }
950    /// ```
951    ///
952    /// # DTLS Role Selection
953    ///
954    /// The answer automatically determines the appropriate DTLS role:
955    /// - Uses `answering_dtls_role` from settings if configured
956    /// - Defaults to `Client` (active) for lower latency
957    /// - Uses `Server` (passive) if remote is ICE-Lite
958    ///
959    /// # Specification
960    ///
961    /// - [W3C RTCPeerConnection.createAnswer]
962    /// - [RFC 9429 Section 5.3] - Generating an Answer
963    ///
964    /// [W3C RTCPeerConnection.createAnswer]: https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-createanswer
965    /// [RFC 9429 Section 5.3]: https://datatracker.ietf.org/doc/html/rfc9429#section-5.3
966    pub fn create_answer(
967        &mut self,
968        _options: Option<RTCAnswerOptions>,
969    ) -> Result<RTCSessionDescription> {
970        if self.remote_description().is_none() {
971            return Err(Error::ErrNoRemoteDescription);
972        }
973
974        if self.peer_connection_state == RTCPeerConnectionState::Closed {
975            return Err(Error::ErrConnectionClosed);
976        }
977
978        if self.signaling_state != RTCSignalingState::HaveRemoteOffer
979            && self.signaling_state != RTCSignalingState::HaveLocalPranswer
980        {
981            return Err(Error::ErrIncorrectSignalingState);
982        }
983
984        let mut connection_role = self.setting_engine.answering_dtls_role.to_connection_role();
985        // RFC 5763 §5: "The answerer MUST use either a setup attribute value of setup:active or
986        // setup:passive." `actpass` is an offer-only value, so an answering role of
987        // `RTCDtlsRole::Auto` — which maps to it — means "no preference" and has to be resolved
988        // to a concrete role here, exactly like `Unspecified`.
989        //
990        // Letting `actpass` through leaves the role genuinely unnegotiated: the answerer falls
991        // back to its own `answering_dtls_role`, and the offerer, seeing no explicit role in the
992        // answer, falls back to *its* configured role. When those agree — e.g. an offerer pinned
993        // to `Client` answered by a peer configured `Auto` — both endpoints become the DTLS
994        // client, both wait for a ClientHello, and the handshake never completes.
995        if matches!(
996            connection_role,
997            ConnectionRole::Unspecified | ConnectionRole::Actpass
998        ) {
999            connection_role = DEFAULT_DTLS_ROLE_ANSWER.to_connection_role();
1000
1001            if let Some(remote_description) = self.remote_description()
1002                && let Some(parsed) = remote_description.parsed.as_ref()
1003                && is_lite_set(parsed)
1004                && !self.setting_engine.candidates.ice_lite
1005            {
1006                connection_role = RTCDtlsRole::Server.to_connection_role();
1007            }
1008        }
1009
1010        let mut d = self.generate_matched_sdp(
1011            false, /*includeUnmatched */
1012            connection_role,
1013            self.setting_engine.ignore_rid_pause_for_recv,
1014        )?;
1015
1016        update_sdp_origin(&mut self.sdp_origin, &mut d);
1017
1018        let sdp = d.marshal();
1019
1020        let answer = RTCSessionDescription {
1021            sdp_type: RTCSdpType::Answer,
1022            sdp,
1023            parsed: Some(d),
1024        };
1025
1026        self.last_answer.clone_from(&answer.sdp);
1027
1028        Ok(answer)
1029    }
1030
1031    /// Sets the local description for this peer connection.
1032    ///
1033    /// This method applies a local SDP description (offer or answer) to the peer
1034    /// connection, updating the local media and transport configuration. It must be
1035    /// called after creating an offer or answer.
1036    ///
1037    /// # Parameters
1038    ///
1039    /// - `local_description`: The session description to set as the local description.
1040    ///   This should be an offer or answer created by `create_offer()` or `create_answer()`.
1041    ///
1042    /// # Returns
1043    ///
1044    /// Returns `Ok(())` on success.
1045    ///
1046    /// # Errors
1047    ///
1048    /// Returns an error if:
1049    /// - The peer connection is closed (`ErrConnectionClosed`)
1050    /// - The SDP type is invalid for the current signaling state
1051    /// - SDP parsing fails
1052    /// - Transport configuration fails
1053    ///
1054    /// # Signaling State Transitions
1055    ///
1056    /// Setting the local description causes signaling state transitions:
1057    ///
1058    /// - **Offer**: `Stable` → `HaveLocalOffer`
1059    /// - **Answer**: `HaveRemoteOffer` → `Stable`
1060    /// - **Pranswer**: `HaveRemoteOffer` → `HaveLocalPranswer`
1061    ///
1062    /// # Examples
1063    ///
1064    /// ## Setting Local Offer
1065    ///
1066    /// ```no_run
1067    /// # use std::time::Instant;
1068    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
1069    ///
1070    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1071    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
1072    ///
1073    /// // Create offer
1074    /// let offer = pc.create_offer(None)?;
1075    ///
1076    /// // Set as local description
1077    /// pc.set_local_description(Instant::now(), offer.clone())?;
1078    ///
1079    /// // Now send offer.sdp to remote peer via signaling
1080    /// // signaling_channel.send(offer.sdp)?;
1081    /// # Ok(())
1082    /// # }
1083    /// ```
1084    ///
1085    /// ## Setting Local Answer
1086    ///
1087    /// ```no_run
1088    /// # use std::time::Instant;
1089    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
1090    /// use rtc::peer_connection::sdp::RTCSessionDescription;
1091    ///
1092    /// # fn example(remote_offer_sdp: String) -> Result<(), Box<dyn std::error::Error>> {
1093    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
1094    ///
1095    /// // Set remote offer first
1096    /// let offer = RTCSessionDescription::offer(remote_offer_sdp)?;
1097    /// pc.set_remote_description(Instant::now(), offer)?;
1098    ///
1099    /// // Create and set local answer
1100    /// let answer = pc.create_answer(None)?;
1101    /// pc.set_local_description(Instant::now(), answer.clone())?;
1102    ///
1103    /// // Send answer to remote peer
1104    /// // signaling_channel.send(answer.sdp)?;
1105    /// # Ok(())
1106    /// # }
1107    /// ```
1108    ///
1109    /// # Empty SDP Handling (JSEP 5.4)
1110    ///
1111    /// If the SDP string is empty, the last offer or answer is reused:
1112    /// - For offers: Uses the last generated offer
1113    /// - For answers: Uses the last generated answer
1114    ///
1115    /// This allows re-applying descriptions without regenerating SDP.
1116    ///
1117    /// # Media and Transport Activation
1118    ///
1119    /// When setting a local answer:
1120    /// - RTP transceivers are activated
1121    /// - SCTP transport is started for data channels
1122    /// - Media can begin flowing
1123    ///
1124    /// # Specification
1125    ///
1126    /// - [W3C RTCPeerConnection.setLocalDescription]
1127    /// - [RFC 9429 Section 5.9] - Applying a Local Description
1128    ///
1129    /// [W3C RTCPeerConnection.setLocalDescription]: https://www.w3.org/TR/webrtc/#dom-peerconnection-setlocaldescription
1130    /// [RFC 9429 Section 5.9]: https://datatracker.ietf.org/doc/html/rfc9429#section-5.9
1131    pub fn set_local_description(
1132        &mut self,
1133        now: Instant,
1134        mut local_description: RTCSessionDescription,
1135    ) -> Result<()> {
1136        if self.peer_connection_state == RTCPeerConnectionState::Closed {
1137            return Err(Error::ErrConnectionClosed);
1138        }
1139
1140        // Apply an ICE restart staged by `create_offer`. A no-op if none was staged, so an offer
1141        // that was created and then discarded leaves the existing session untouched.
1142        if self.ice_transport().has_pending_restart() {
1143            self.apply_ice_restart(now)?;
1144        }
1145
1146        // JSEP 5.4
1147        if local_description.sdp.is_empty() {
1148            match local_description.sdp_type {
1149                RTCSdpType::Answer | RTCSdpType::Pranswer => {
1150                    local_description.sdp.clone_from(&self.last_answer);
1151                }
1152                RTCSdpType::Offer => {
1153                    local_description.sdp.clone_from(&self.last_offer);
1154                }
1155                RTCSdpType::Rollback => {
1156                    // WebRTC spec: rollback SDP is ignored, empty is allowed
1157                }
1158                _ => return Err(Error::ErrPeerConnSDPTypeInvalidValueSetLocalDescription),
1159            }
1160        }
1161
1162        // Parse SDP (skip for rollback as content is ignored per spec)
1163        if local_description.sdp_type != RTCSdpType::Rollback {
1164            local_description.parsed = Some(local_description.unmarshal()?);
1165        }
1166        self.set_description(&local_description, StateChangeOp::SetLocal)?;
1167
1168        let we_answer = local_description.sdp_type == RTCSdpType::Answer;
1169        if we_answer && let Some(parsed_local_description) = &local_description.parsed {
1170            // WebRTC Spec 1.0 https://www.w3.org/TR/webrtc/
1171            // Section 4.4.1.5
1172            for media in &parsed_local_description.media_descriptions {
1173                let mid_value = match get_mid_value(media) {
1174                    Some(mid) if !mid.is_empty() => mid,
1175                    _ => return Err(Error::ErrPeerConnLocalDescriptionWithoutMidValue),
1176                };
1177
1178                if media.is_webrtc_datachannel() {
1179                    continue;
1180                }
1181
1182                let i = match RTCPeerConnection::find_by_mid(mid_value, &self.rtp_transceivers) {
1183                    Some(i) => i,
1184                    None => return Err(Error::ErrPeerConnTransceiverMidNil),
1185                };
1186
1187                let kind = RtpCodecKind::from(media.media_name.media.as_str());
1188                let mut direction = get_peer_direction(media);
1189                if kind == RtpCodecKind::Unspecified
1190                    || direction == RTCRtpTransceiverDirection::Unspecified
1191                {
1192                    continue;
1193                }
1194
1195                // If a transceiver is created by applying a remote description that has recvonly transceiver,
1196                // it will have no sender. In this case, the transceiver's current direction is set to inactive so
1197                // that the transceiver can be reused by next AddTrack.
1198                if direction == RTCRtpTransceiverDirection::Sendonly
1199                    && self.rtp_transceivers[i].sender().is_none()
1200                {
1201                    direction = RTCRtpTransceiverDirection::Inactive;
1202                }
1203
1204                self.rtp_transceivers[i].set_current_direction(direction);
1205            }
1206
1207            if let Some(remote_description) = self.remote_description().cloned()
1208                && let Some(parsed_remote_description) = remote_description.parsed.as_ref()
1209            {
1210                // only start sctp transport if application media has been negotiated
1211                if let (Some(local_application_media), Some(remote_application_media)) = (
1212                    get_application_media(parsed_local_description),
1213                    get_application_media(parsed_remote_description),
1214                ) {
1215                    let (dtls_role, remote_caps, local_sctp_port, remote_sctp_port) = (
1216                        self.dtls_transport().role(),
1217                        SCTPTransportCapabilities {
1218                            max_message_size: get_application_media_section_max_message_size(
1219                                remote_application_media,
1220                            )
1221                            .unwrap_or(SctpMaxMessageSize::DEFAULT_MESSAGE_SIZE),
1222                        },
1223                        get_application_media_section_sctp_port(local_application_media)
1224                            .unwrap_or(5000),
1225                        get_application_media_section_sctp_port(remote_application_media)
1226                            .unwrap_or(5000),
1227                    );
1228
1229                    // we_answer: we first call set_remote_description,
1230                    // then, we create_answer() and set_local_description() here
1231                    // Now we should have done SDP negotiation.
1232                    // Therefore, it is ready to start sctp and rtp.
1233                    self.sctp_transport_mut().start(
1234                        dtls_role,
1235                        remote_caps,
1236                        local_sctp_port,
1237                        remote_sctp_port,
1238                    )?;
1239                }
1240                self.start_rtp(remote_description)?;
1241            }
1242        }
1243
1244        self.ice_transport_mut().ice_gathering_state = RTCIceGatheringState::Gathering;
1245
1246        Ok(())
1247    }
1248
1249    /// Returns the local session description.
1250    ///
1251    /// Returns `pending_local_description` if it is not null, otherwise returns
1252    /// `current_local_description`. This property is used to determine if
1253    /// `set_local_description` has already been called.
1254    ///
1255    /// # Specification
1256    ///
1257    /// See [localDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-localdescription)
1258    pub fn local_description(&self) -> Option<RTCSessionDescription> {
1259        if let Some(pending_local_description) = self.pending_local_description() {
1260            return Some(pending_local_description);
1261        }
1262        self.current_local_description()
1263    }
1264
1265    /// Returns the current local description as last successfully negotiated since
1266    /// the last negotiation completed.
1267    ///
1268    /// This represents the local description from the last offer/answer exchange that was
1269    /// successfully applied, not including any offers currently being negotiated.
1270    ///
1271    /// Returns `None` if there is no current local description (e.g., before initial negotiation).
1272    ///
1273    /// # Specification
1274    ///
1275    /// See [currentLocalDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-currentlocaldesc)
1276    pub fn current_local_description(&self) -> Option<RTCSessionDescription> {
1277        self.populate_local_candidates(self.current_local_description.as_ref())
1278    }
1279
1280    /// Returns the pending local description if it exists.
1281    ///
1282    /// This represents the local description from a call to `set_local_description()` whose
1283    /// corresponding remote description has not yet been applied. This is `None` if negotiation
1284    /// is not in progress or if a rollback has been performed.
1285    ///
1286    /// Returns `None` if there is no pending local description.
1287    ///
1288    /// # Specification
1289    ///
1290    /// See [pendingLocalDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-pendinglocaldesc)
1291    pub fn pending_local_description(&self) -> Option<RTCSessionDescription> {
1292        self.populate_local_candidates(self.pending_local_description.as_ref())
1293    }
1294
1295    /// Returns whether the remote peer supports trickle ICE.
1296    ///
1297    /// This value is determined from the remote SDP description after `set_remote_description()`
1298    /// is called. It checks for "trickle" in the "ice-options" attribute per
1299    /// RFC 8838 and RFC 9429 section 4.1.17.
1300    ///
1301    /// Returns:
1302    /// - `None` if no remote description has been set yet (unknown)
1303    /// - `Some(true)` if the remote peer indicated trickle ICE support
1304    /// - `Some(false)` if the remote peer did not indicate support
1305    ///
1306    /// # Specification
1307    ///
1308    /// See [canTrickleIceCandidates](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-cantrickleicecandidates)
1309    pub fn can_trickle_ice_candidates(&self) -> Option<bool> {
1310        self.can_trickle_ice_candidates
1311    }
1312
1313    /// Sets the remote description as part of the offer/answer negotiation.
1314    ///
1315    /// This changes the remote description associated with the connection. This description
1316    /// specifies the properties of the remote end of the connection, including the media format.
1317    ///
1318    /// # Parameters
1319    ///
1320    /// * `remote_description` - The remote session description to set.
1321    ///
1322    /// # Errors
1323    ///
1324    /// Returns an error if:
1325    /// - The peer connection is closed
1326    /// - The SDP cannot be parsed
1327    /// - The media engine fails to update from the remote description
1328    ///
1329    /// # Specification
1330    ///
1331    /// See [setRemoteDescription](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setremotedescription!overload-1)
1332    pub fn set_remote_description(
1333        &mut self,
1334        now: Instant,
1335        mut remote_description: RTCSessionDescription,
1336    ) -> Result<()> {
1337        if self.peer_connection_state == RTCPeerConnectionState::Closed {
1338            return Err(Error::ErrConnectionClosed);
1339        }
1340
1341        let is_renegotiation = self.current_remote_description.is_some();
1342
1343        // Parse SDP (skip for rollback as content is ignored per spec)
1344        if remote_description.sdp_type != RTCSdpType::Rollback {
1345            remote_description.parsed = Some(remote_description.unmarshal()?);
1346        }
1347        self.set_description(&remote_description, StateChangeOp::SetRemote)?;
1348
1349        if let Some(parsed_remote_description) = &remote_description.parsed {
1350            self.media_engine
1351                .update_from_remote_description(parsed_remote_description)?;
1352
1353            // Detect trickle ICE support from remote SDP (RFC 8838/RFC 9429 section 4.1.17)
1354            // Check for "trickle" in space-separated "ice-options" attribute values
1355            let has_trickle_ice = has_ice_trickle_option(parsed_remote_description);
1356
1357            match remote_description.sdp_type {
1358                RTCSdpType::Offer | RTCSdpType::Answer | RTCSdpType::Pranswer => {
1359                    self.can_trickle_ice_candidates = Some(has_trickle_ice);
1360                }
1361                _ => {
1362                    // Rollback or other types: reset to unknown
1363                    self.can_trickle_ice_candidates = None;
1364                }
1365            }
1366
1367            // Disable RTX/FEC on RTPSenders if the remote didn't support it
1368            for transceiver in &mut self.rtp_transceivers {
1369                if let Some(sender) = transceiver.sender_mut() {
1370                    let (is_rtx_enabled, is_fec_enabled) = (
1371                        self.media_engine
1372                            .is_rtx_enabled(sender.kind(), RTCRtpTransceiverDirection::Sendonly),
1373                        self.media_engine
1374                            .is_fec_enabled(sender.kind(), RTCRtpTransceiverDirection::Sendonly),
1375                    );
1376                    sender.configure_rtx_and_fec(is_rtx_enabled, is_fec_enabled);
1377                }
1378            }
1379
1380            let we_offer = remote_description.sdp_type == RTCSdpType::Answer;
1381
1382            // Extract media descriptions to avoid borrowing conflicts
1383            let media_descriptions = self
1384                .remote_description()
1385                .as_ref()
1386                .and_then(|r| r.parsed.as_ref())
1387                .map(|parsed| parsed.media_descriptions.clone());
1388
1389            if let Some(media_descriptions) = media_descriptions {
1390                if !we_offer {
1391                    for media in &media_descriptions {
1392                        let mid_value = match get_mid_value(media) {
1393                            Some(mid) if !mid.is_empty() => mid,
1394                            _ => return Err(Error::ErrPeerConnRemoteDescriptionWithoutMidValue),
1395                        };
1396
1397                        if media.is_webrtc_datachannel() {
1398                            continue;
1399                        }
1400
1401                        let kind = RtpCodecKind::from(media.media_name.media.as_str());
1402                        let direction = get_peer_direction(media);
1403                        if kind == RtpCodecKind::Unspecified
1404                            || direction == RTCRtpTransceiverDirection::Unspecified
1405                        {
1406                            continue;
1407                        }
1408
1409                        let transceiver = if let Some(i) =
1410                            RTCPeerConnection::find_by_mid(mid_value, &self.rtp_transceivers)
1411                        {
1412                            if direction == RTCRtpTransceiverDirection::Inactive {
1413                                self.rtp_transceivers[i]
1414                                    .stop(&self.media_engine, &mut self.interceptor)?;
1415                            }
1416                            Some(&mut self.rtp_transceivers[i])
1417                        } else {
1418                            RTCPeerConnection::satisfy_type_and_direction(
1419                                kind,
1420                                direction,
1421                                &mut self.rtp_transceivers,
1422                            )
1423                        };
1424
1425                        if let Some(transceiver) = transceiver {
1426                            if direction == RTCRtpTransceiverDirection::Recvonly {
1427                                if transceiver.direction() == RTCRtpTransceiverDirection::Sendrecv {
1428                                    transceiver.set_direction(RTCRtpTransceiverDirection::Sendonly);
1429                                } else if transceiver.direction()
1430                                    == RTCRtpTransceiverDirection::Recvonly
1431                                {
1432                                    transceiver.set_direction(RTCRtpTransceiverDirection::Inactive);
1433                                }
1434                            } else if direction == RTCRtpTransceiverDirection::Sendrecv {
1435                                if transceiver.direction() == RTCRtpTransceiverDirection::Sendonly {
1436                                    transceiver.set_direction(RTCRtpTransceiverDirection::Sendrecv);
1437                                } else if transceiver.direction()
1438                                    == RTCRtpTransceiverDirection::Inactive
1439                                {
1440                                    transceiver.set_direction(RTCRtpTransceiverDirection::Recvonly);
1441                                }
1442                            } else if direction == RTCRtpTransceiverDirection::Sendonly
1443                                && transceiver.direction() == RTCRtpTransceiverDirection::Inactive
1444                            {
1445                                transceiver.set_direction(RTCRtpTransceiverDirection::Recvonly);
1446                            }
1447
1448                            transceiver.set_codec_preferences_from_remote_description(
1449                                media,
1450                                &self.media_engine,
1451                            )?;
1452
1453                            if transceiver.mid().is_none() {
1454                                transceiver.set_mid(mid_value.to_string())?;
1455                            }
1456                        } else {
1457                            let local_direction =
1458                                if direction == RTCRtpTransceiverDirection::Recvonly {
1459                                    RTCRtpTransceiverDirection::Sendonly
1460                                } else {
1461                                    RTCRtpTransceiverDirection::Recvonly
1462                                };
1463
1464                            let mut transceiver = RTCRtpTransceiverInternal::new(
1465                                kind,
1466                                None,
1467                                RTCRtpTransceiverInit {
1468                                    direction: local_direction,
1469                                    streams: vec![],
1470                                    send_encodings: vec![],
1471                                },
1472                            );
1473
1474                            transceiver.set_codec_preferences_from_remote_description(
1475                                media,
1476                                &self.media_engine,
1477                            )?;
1478
1479                            if transceiver.mid().is_none() {
1480                                transceiver.set_mid(mid_value.to_string())?;
1481                            }
1482
1483                            // Mark as implicitly created by a remote offer so that, if this offer
1484                            // is later rolled back, the transceiver is stopped and removed
1485                            // (RFC 9429, Section 5.7) — unless a track is attached via add_track.
1486                            transceiver.set_created_by_remote_description(true);
1487
1488                            self.add_rtp_transceiver(transceiver);
1489                        }
1490                    }
1491                } else {
1492                    // we_offer
1493                    // WebRTC Spec 1.0 https://www.w3.org/TR/webrtc/
1494                    // 4.5.9.2
1495                    // This is an answer from the remote.
1496                    for media in &media_descriptions {
1497                        let mid_value = match get_mid_value(media) {
1498                            Some(mid) if !mid.is_empty() => mid,
1499                            _ => return Err(Error::ErrPeerConnRemoteDescriptionWithoutMidValue),
1500                        };
1501
1502                        if media.is_webrtc_datachannel() {
1503                            continue;
1504                        }
1505
1506                        let kind = RtpCodecKind::from(media.media_name.media.as_str());
1507                        let mut direction = get_peer_direction(media);
1508                        if kind == RtpCodecKind::Unspecified
1509                            || direction == RTCRtpTransceiverDirection::Unspecified
1510                        {
1511                            continue;
1512                        }
1513
1514                        let transceiver = if let Some(i) =
1515                            RTCPeerConnection::find_by_mid(mid_value, &self.rtp_transceivers)
1516                        {
1517                            &mut self.rtp_transceivers[i]
1518                        } else {
1519                            return Err(Error::ErrPeerConnTransceiverMidNil);
1520                        };
1521
1522                        // reverse direction if it was a remote answer
1523                        if direction == RTCRtpTransceiverDirection::Sendonly {
1524                            direction = RTCRtpTransceiverDirection::Recvonly;
1525                        } else if direction == RTCRtpTransceiverDirection::Recvonly {
1526                            direction = RTCRtpTransceiverDirection::Sendonly;
1527                        }
1528
1529                        transceiver.set_current_direction(direction);
1530
1531                        transceiver.set_codec_preferences_from_remote_description(
1532                            media,
1533                            &self.media_engine,
1534                        )?;
1535                    }
1536                }
1537            }
1538
1539            let (remote_ufrag, remote_pwd, candidates) =
1540                extract_ice_details(parsed_remote_description)?;
1541
1542            if is_renegotiation
1543                && self
1544                    .ice_transport()
1545                    .have_remote_credentials_change(&remote_ufrag, &remote_pwd)
1546            {
1547                // An ICE Restart only happens implicitly for a set_remote_description of type offer
1548
1549                if !we_offer {
1550                    // The answerer restarts in one step: it has `now`, and the answer it generates
1551                    // next must already carry the new local credentials.
1552                    self.stage_ice_restart()?;
1553                    self.apply_ice_restart(now)?;
1554                }
1555
1556                self.ice_transport_mut()
1557                    .set_remote_credentials(remote_ufrag.clone(), remote_pwd.clone())?;
1558            }
1559
1560            for candidate in candidates {
1561                self.ice_transport_mut().add_remote_candidate(candidate)?;
1562            }
1563
1564            if !is_renegotiation {
1565                let remote_is_lite = is_lite_set(parsed_remote_description);
1566
1567                let (remote_fingerprint, remote_fingerprint_hash) =
1568                    extract_fingerprint(parsed_remote_description)?;
1569
1570                // If one of the agents is lite and the other one is not, the lite agent must be the controlling agent.
1571                // If both or neither agents are lite the offering agent is controlling.
1572                // RFC 8445 S6.1.1
1573                let local_ice_role = if (we_offer
1574                    && remote_is_lite == self.setting_engine.candidates.ice_lite)
1575                    || (remote_is_lite && !self.setting_engine.candidates.ice_lite)
1576                {
1577                    RTCIceRole::Controlling
1578                } else {
1579                    RTCIceRole::Controlled
1580                };
1581
1582                let remote_dtls_role = RTCDtlsRole::from(parsed_remote_description);
1583                log::trace!(
1584                    "start_transports: local_ice_role={local_ice_role}, remote_dtls_role={remote_dtls_role}"
1585                );
1586
1587                self.start_transports(
1588                    now,
1589                    local_ice_role,
1590                    RTCIceParameters {
1591                        username_fragment: remote_ufrag,
1592                        password: remote_pwd,
1593                        ice_lite: remote_is_lite,
1594                    },
1595                    RTCDtlsParameters {
1596                        role: remote_dtls_role,
1597                        fingerprints: vec![RTCDtlsFingerprint {
1598                            algorithm: remote_fingerprint_hash,
1599                            value: remote_fingerprint,
1600                        }],
1601                    },
1602                )?;
1603            }
1604
1605            if we_offer
1606                && let Some(parsed_local_description) = self
1607                    .current_local_description
1608                    .as_ref()
1609                    .and_then(|desc| desc.parsed.as_ref())
1610            {
1611                // only start sctp transport if application media has been negotiated
1612                if let (Some(local_application_media), Some(remote_application_media)) = (
1613                    get_application_media(parsed_local_description),
1614                    get_application_media(parsed_remote_description),
1615                ) {
1616                    let (dtls_role, remote_caps, local_sctp_port, remote_sctp_port) = (
1617                        self.dtls_transport().role(),
1618                        SCTPTransportCapabilities {
1619                            max_message_size: get_application_media_section_max_message_size(
1620                                remote_application_media,
1621                            )
1622                            .unwrap_or(SctpMaxMessageSize::DEFAULT_MESSAGE_SIZE),
1623                        },
1624                        get_application_media_section_sctp_port(local_application_media)
1625                            .unwrap_or(5000),
1626                        get_application_media_section_sctp_port(remote_application_media)
1627                            .unwrap_or(5000),
1628                    );
1629
1630                    // we_offer: we create_offer() and set_local_description() first
1631                    // then, after call set_remote_description here,
1632                    // Now we should have done SDP negotiation.
1633                    // Therefore, it is ready to start sctp and rtp.
1634                    self.sctp_transport_mut().start(
1635                        dtls_role,
1636                        remote_caps,
1637                        local_sctp_port,
1638                        remote_sctp_port,
1639                    )?;
1640                }
1641                self.start_rtp(remote_description)?;
1642            }
1643        }
1644
1645        Ok(())
1646    }
1647
1648    /// Returns the remote session description.
1649    ///
1650    /// Returns `pending_remote_description` if it is not null, otherwise returns
1651    /// `current_remote_description`. This property is used to determine if
1652    /// `set_remote_description` has already been called.
1653    ///
1654    /// # Specification
1655    ///
1656    /// See [remoteDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-remotedescription)
1657    pub fn remote_description(&self) -> Option<&RTCSessionDescription> {
1658        if self.pending_remote_description.is_some() {
1659            self.pending_remote_description.as_ref()
1660        } else {
1661            self.current_remote_description.as_ref()
1662        }
1663    }
1664
1665    /// Returns the current remote description as last successfully negotiated since
1666    /// the last negotiation completed.
1667    ///
1668    /// This represents the remote description from the last offer/answer exchange that was
1669    /// successfully applied, not including any offers currently being negotiated.
1670    ///
1671    /// Returns `None` if there is no current remote description (e.g., before initial negotiation).
1672    ///
1673    /// # Specification
1674    ///
1675    /// See [currentRemoteDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-currentremotedesc)
1676    pub fn current_remote_description(&self) -> Option<&RTCSessionDescription> {
1677        self.current_remote_description.as_ref()
1678    }
1679
1680    /// Returns the pending remote description if it exists.
1681    ///
1682    /// This represents the remote description from a call to `set_remote_description()` whose
1683    /// corresponding local description has not yet been applied. This is `None` if negotiation
1684    /// is not in progress or if a rollback has been performed.
1685    ///
1686    /// Returns `None` if there is no pending remote description.
1687    ///
1688    /// # Specification
1689    ///
1690    /// See [pendingRemoteDescription](https://www.w3.org/TR/webrtc/#dom-peerconnection-pendingremotedesc)
1691    pub fn pending_remote_description(&self) -> Option<&RTCSessionDescription> {
1692        self.pending_remote_description.as_ref()
1693    }
1694
1695    /// Adds a remote ICE candidate to the peer connection.
1696    ///
1697    /// This method provides a remote candidate to the ICE agent. When the remote peer
1698    /// gathers ICE candidates and sends them over the signaling channel, this method
1699    /// should be called to add each candidate.
1700    ///
1701    /// # Parameters
1702    ///
1703    /// * `remote_candidate` - The ICE candidate initialization data.
1704    ///
1705    /// # Errors
1706    ///
1707    /// Returns an error if:
1708    /// - No remote description has been set
1709    /// - The candidate string is invalid
1710    ///
1711    /// # Specification
1712    ///
1713    /// See [addIceCandidate](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addicecandidate!overload-1)
1714    pub fn add_remote_candidate(&mut self, remote_candidate: RTCIceCandidateInit) -> Result<()> {
1715        if self.remote_description().is_none() {
1716            return Err(Error::ErrNoRemoteDescription);
1717        }
1718
1719        let candidate_value = match remote_candidate.candidate.strip_prefix("candidate:") {
1720            Some(s) => s,
1721            None => remote_candidate.candidate.as_str(),
1722        };
1723
1724        if !candidate_value.is_empty() {
1725            self.add_ice_remote_candidate(candidate_value)?;
1726        }
1727
1728        Ok(())
1729    }
1730
1731    /// Adds a local ICE candidate to the peer connection.
1732    ///
1733    /// This has no W3C counterpart: a browser's ICE agent gathers its own candidates, but this
1734    /// crate performs no I/O, so the application owns the sockets and therefore owns gathering.
1735    /// Every local candidate the connection is to advertise must be handed to this method.
1736    ///
1737    /// # Signalling the end of gathering
1738    ///
1739    /// An **empty candidate string** is the end-of-gathering sentinel. It advertises no
1740    /// candidate; it moves the ICE gathering state to [`RTCIceGatheringState::Complete`] and
1741    /// emits [`RTCPeerConnectionEvent::OnIceGatheringStateChangeEvent`] with that state. No
1742    /// other call reaches `Complete`, so a caller that never sends it leaves the connection
1743    /// reporting `Gathering` forever. Send it once, after the last real candidate:
1744    ///
1745    /// ```no_run
1746    /// # use std::time::Instant;
1747    /// # use rtc::peer_connection::RTCPeerConnectionBuilder;
1748    /// use rtc::peer_connection::transport::RTCIceCandidateInit;
1749    ///
1750    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
1751    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
1752    /// // ... add every gathered candidate first ...
1753    /// pc.add_local_candidate(RTCIceCandidateInit::default())?;
1754    /// # Ok(())
1755    /// # }
1756    /// ```
1757    ///
1758    /// # Parameters
1759    ///
1760    /// - `local_candidate`: The ICE candidate initialization data, or a value whose `candidate`
1761    ///   field is empty to signal end-of-gathering. For candidates of type `srflx` (server
1762    ///   reflexive) or `relay`, set `url` to the STUN/TURN server the candidate was gathered
1763    ///   from, so that `getStats` attributes it correctly.
1764    ///
1765    /// # Errors
1766    ///
1767    /// Returns an error if a non-empty candidate string cannot be parsed.
1768    ///
1769    /// [`RTCIceGatheringState::Complete`]: crate::peer_connection::state::RTCIceGatheringState::Complete
1770    /// [`RTCPeerConnectionEvent::OnIceGatheringStateChangeEvent`]: crate::peer_connection::event::RTCPeerConnectionEvent::OnIceGatheringStateChangeEvent
1771    pub fn add_local_candidate(&mut self, local_candidate: RTCIceCandidateInit) -> Result<()> {
1772        let candidate_value = match local_candidate.candidate.strip_prefix("candidate:") {
1773            Some(s) => s,
1774            None => local_candidate.candidate.as_str(),
1775        };
1776
1777        if !candidate_value.is_empty() {
1778            self.add_ice_local_candidate(candidate_value, local_candidate.url.as_deref())?;
1779        } else {
1780            self.ice_transport_mut().ice_gathering_state = RTCIceGatheringState::Complete;
1781            // Emit OnIceGatheringStateChangeEvent
1782            self.pipeline_context.event_outs.push_back(
1783                RTCPeerConnectionEvent::OnIceGatheringStateChangeEvent(
1784                    RTCIceGatheringState::Complete,
1785                ),
1786            );
1787        }
1788
1789        Ok(())
1790    }
1791
1792    /// Tells the peer connection that ICE should be restarted.
1793    ///
1794    /// This method causes the next call to `create_offer` to generate an offer that
1795    /// will restart ICE. This is useful when network conditions change or the connection
1796    /// fails.
1797    ///
1798    /// # Specification
1799    ///
1800    /// See [restartIce](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-restartice)
1801    pub fn restart_ice(&mut self) {
1802        self.ice_restart_requested = Some(RTCOfferOptions { ice_restart: true });
1803    }
1804
1805    /// Returns the current configuration of this peer connection.
1806    ///
1807    /// The returned reference is to the current configuration. To modify the configuration,
1808    /// use `set_configuration`.
1809    ///
1810    /// # Specification
1811    ///
1812    /// See [getConfiguration](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration)
1813    pub fn get_configuration(&self) -> &RTCConfiguration {
1814        &self.configuration
1815    }
1816
1817    /// Updates the configuration of this peer connection.
1818    ///
1819    /// Only the fields the W3C algorithm permits changing after construction are applied;
1820    /// attempting to change the peer identity or the certificate set is an error.
1821    ///
1822    /// # Parameters
1823    ///
1824    /// - `configuration`: The configuration to apply.
1825    ///
1826    /// # Errors
1827    ///
1828    /// Returns an error if:
1829    /// - The peer connection is closed (`ErrConnectionClosed`)
1830    /// - The peer identity differs from the one already set (`ErrModifyingPeerIdentity`)
1831    /// - The number of certificates differs from the one already set (`ErrModifyingCertificates`)
1832    ///
1833    /// # Specification
1834    ///
1835    /// See [setConfiguration](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration)
1836    pub fn set_configuration(&mut self, configuration: RTCConfiguration) -> Result<()> {
1837        // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2)
1838        if self.peer_connection_state == RTCPeerConnectionState::Closed {
1839            return Err(Error::ErrConnectionClosed);
1840        }
1841
1842        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
1843        if !configuration.peer_identity.is_empty() {
1844            if configuration.peer_identity != self.configuration.peer_identity {
1845                return Err(Error::ErrModifyingPeerIdentity);
1846            }
1847            self.configuration.peer_identity = configuration.peer_identity;
1848        }
1849
1850        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #4)
1851        if !configuration.certificates.is_empty() {
1852            if configuration.certificates.len() != self.configuration.certificates.len() {
1853                return Err(Error::ErrModifyingCertificates);
1854            }
1855
1856            self.configuration.certificates = configuration.certificates;
1857        }
1858
1859        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
1860
1861        if configuration.bundle_policy != self.configuration.bundle_policy {
1862            return Err(Error::ErrModifyingBundlePolicy);
1863        }
1864        self.configuration.bundle_policy = configuration.bundle_policy;
1865
1866        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
1867        if configuration.rtcp_mux_policy != self.configuration.rtcp_mux_policy {
1868            return Err(Error::ErrModifyingRTCPMuxPolicy);
1869        }
1870        self.configuration.rtcp_mux_policy = configuration.rtcp_mux_policy;
1871
1872        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #7)
1873        if configuration.ice_candidate_pool_size != 0 {
1874            if self.configuration.ice_candidate_pool_size != configuration.ice_candidate_pool_size
1875                && self.local_description().is_some()
1876            {
1877                return Err(Error::ErrModifyingICECandidatePoolSize);
1878            }
1879            self.configuration.ice_candidate_pool_size = configuration.ice_candidate_pool_size;
1880        }
1881
1882        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #8)
1883
1884        self.configuration.ice_transport_policy = configuration.ice_transport_policy;
1885
1886        // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11)
1887        if !configuration.ice_servers.is_empty() {
1888            // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3)
1889            for server in &configuration.ice_servers {
1890                server.validate()?;
1891            }
1892            self.configuration.ice_servers = configuration.ice_servers
1893        }
1894
1895        Ok(())
1896    }
1897
1898    /// Creates a new data channel with the given label.
1899    ///
1900    /// The returned handle borrows the peer connection. To reach the channel again later,
1901    /// keep its [`RTCDataChannelId`] (from [`RTCDataChannel::id`]) and pass it to
1902    /// [`Self::data_channel`].
1903    ///
1904    /// A channel created here is not immediately usable: unless it was negotiated
1905    /// out-of-band, its SCTP stream is established by the in-band DCEP handshake, and sending
1906    /// before then fails. Wait for [`RTCDataChannelEvent::OnOpen`].
1907    ///
1908    /// # Parameters
1909    ///
1910    /// - `label`: The channel label. Labels need not be unique.
1911    /// - `options`: Optional configuration such as ordering, reliability and out-of-band
1912    ///   negotiation. Defaults are used when `None`.
1913    ///
1914    /// # Errors
1915    ///
1916    /// Returns an error if:
1917    /// - The peer connection is closed (`ErrConnectionClosed`)
1918    /// - The label or sub-protocol exceeds the length the DCEP OPEN message can carry
1919    ///
1920    /// # Specification
1921    ///
1922    /// See [createDataChannel](https://www.w3.org/TR/webrtc/#dom-peerconnection-createdatachannel)
1923    ///
1924    /// [`RTCDataChannelId`]: crate::data_channel::RTCDataChannelId
1925    /// [`RTCDataChannel::id`]: crate::data_channel::RTCDataChannel::id
1926    /// [`RTCDataChannelEvent::OnOpen`]: crate::peer_connection::event::RTCDataChannelEvent::OnOpen
1927    pub fn create_data_channel(
1928        &mut self,
1929        label: &str,
1930        options: Option<RTCDataChannelInit>,
1931    ) -> Result<RTCDataChannel<'_>> {
1932        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #2)
1933        if self.peer_connection_state == RTCPeerConnectionState::Closed {
1934            return Err(Error::ErrConnectionClosed);
1935        }
1936
1937        let mut params = DataChannelParameters {
1938            label: label.to_owned(),
1939            ..Default::default()
1940        };
1941
1942        // `None` means "the dictionary defaults", which is what `RTCDataChannelInit::default()`
1943        // spells out. Taking that route rather than leaving `params` on its derived default
1944        // keeps a single definition of those defaults — notably `ordered`, which is `true`.
1945        let options = options.unwrap_or_default();
1946
1947        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #16)
1948        if options.max_packet_life_time.is_some() && options.max_retransmits.is_some() {
1949            return Err(Error::ErrRetransmitsOrPacketLifeTime);
1950        }
1951
1952        // Ordered indicates if data is allowed to be delivered out of order. The
1953        // default value of true, guarantees that data will be delivered in order.
1954        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #9)
1955        params.ordered = options.ordered;
1956
1957        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #7)
1958        params.max_packet_life_time = options.max_packet_life_time;
1959
1960        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #8)
1961        params.max_retransmits = options.max_retransmits;
1962
1963        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #10)
1964        params.protocol = options.protocol;
1965
1966        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #11)
1967        if params.protocol.len() > 65535 {
1968            return Err(Error::ErrProtocolTooLarge);
1969        }
1970
1971        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #12)
1972        //
1973        // `negotiated` doubles as the out-of-band stream id. When it is set the id is fixed by
1974        // the application and known now; when it is not, the channel is announced in-band and
1975        // its stream id has to wait for the DTLS role, per RFC 8832 §6. Nothing is guessed
1976        // here — see `assign_stream_ids`.
1977        params.negotiated = options.negotiated;
1978
1979        // The registry assigns the handle. Note this happens before any dial: the channel is
1980        // addressable from the moment it exists, whether or not it has a stream id yet.
1981        let id = self
1982            .data_channels
1983            .insert(RTCDataChannelInternal::new(params));
1984
1985        // https://www.w3.org/TR/webrtc/#peer-to-peer-data-api (Step #23)
1986        // Open the channel's data transport immediately when an SCTP association already
1987        // exists. The DTLS role is necessarily resolved by then, so a stream id can be
1988        // assigned right here rather than waiting for a connected procedure that has passed.
1989        //
1990        // The role guard is not redundant with the association check. In practice an
1991        // association only exists once a description has been applied, which resolves the
1992        // role — but if it somehow is not resolved, there is no correct parity to pick, and
1993        // "wait for the connected procedure" is the right answer rather than an error. That is
1994        // the ordinary path for every channel created before negotiation.
1995        let dtls_role = self.dtls_transport().role();
1996        if let Some(handle) = self
1997            .sctp_transport()
1998            .sctp_associations
1999            .keys()
2000            .next()
2001            .copied()
2002            && matches!(dtls_role, RTCDtlsRole::Client | RTCDtlsRole::Server)
2003        {
2004            let max_channels = self.sctp_transport().max_channels();
2005            self.data_channels
2006                .assign_stream_ids(dtls_role, max_channels)?;
2007
2008            let data_channel = self
2009                .data_channels
2010                .get_mut(&id)
2011                .ok_or(Error::ErrDataChannelNotExisted)?;
2012            if data_channel.ready_state == RTCDataChannelState::Connecting
2013                && data_channel.data_channel.is_none()
2014            {
2015                data_channel.dial(handle.0)?;
2016            }
2017        }
2018
2019        self.trigger_negotiation_needed();
2020
2021        Ok(RTCDataChannel {
2022            id,
2023            peer_connection: self,
2024        })
2025    }
2026
2027    /// Returns an iterator over the `RTCRtpSender` objects.
2028    ///
2029    /// The `RTCRtpSender` objects represent the media streams that are being sent
2030    /// to the remote peer.
2031    ///
2032    /// # Specification
2033    ///
2034    /// See [getSenders](https://www.w3.org/TR/webrtc/#dom-peerconnection-getsenders)
2035    pub fn get_senders(&self) -> impl Iterator<Item = RTCRtpSenderId> + use<'_> {
2036        self.rtp_transceivers
2037            .iter()
2038            .enumerate()
2039            .filter(|(_, transceiver)| transceiver.direction().has_send())
2040            .map(|(id, _)| RTCRtpSenderId(id))
2041    }
2042
2043    /// Returns an iterator over the `RTCRtpReceiver` objects.
2044    ///
2045    /// The `RTCRtpReceiver` objects represent the media streams that are being received
2046    /// from the remote peer.
2047    ///
2048    /// # Specification
2049    ///
2050    /// See [getReceivers](https://www.w3.org/TR/webrtc/#dom-peerconnection-getreceivers)
2051    pub fn get_receivers(&self) -> impl Iterator<Item = RTCRtpReceiverId> + use<'_> {
2052        self.rtp_transceivers
2053            .iter()
2054            .enumerate()
2055            .filter(|(_, transceiver)| transceiver.direction().has_recv())
2056            .map(|(id, _)| RTCRtpReceiverId(id))
2057    }
2058
2059    /// Returns an iterator over the `RTCRtpTransceiver` objects.
2060    ///
2061    /// The `RTCRtpTransceiver` objects represent the combination of an `RTCRtpSender`
2062    /// and an `RTCRtpReceiver` that share a common mid.
2063    ///
2064    /// # Specification
2065    ///
2066    /// See [getTransceivers](https://www.w3.org/TR/webrtc/#dom-peerconnection-gettranseceivers)
2067    pub fn get_transceivers(&self) -> impl Iterator<Item = RTCRtpTransceiverId> {
2068        0..self.rtp_transceivers.len()
2069    }
2070
2071    /// Adds a media track to the peer connection.
2072    ///
2073    /// This method adds a track to the connection, either by finding an existing transceiver
2074    /// that can be reused, or by creating a new transceiver. The track represents media
2075    /// (audio or video) that will be sent to the remote peer.
2076    ///
2077    /// # Parameters
2078    ///
2079    /// * `track` - The media stream track to add.
2080    ///
2081    /// # Returns
2082    ///
2083    /// Returns the ID of the `RTCRtpSender` that will send this track.
2084    ///
2085    /// # Errors
2086    ///
2087    /// Returns an error if the peer connection is closed.
2088    ///
2089    /// # Specification
2090    ///
2091    /// See [addTrack](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtrack)
2092    pub fn add_track(&mut self, track: MediaStreamTrack) -> Result<RTCRtpSenderId> {
2093        if self.peer_connection_state == RTCPeerConnectionState::Closed {
2094            return Err(Error::ErrConnectionClosed);
2095        }
2096
2097        let send_encodings = self.send_encodings_from_track(&track);
2098        let (track, send_encodings, codec_preferences) =
2099            self.normalize_sender_track(track, send_encodings)?;
2100        for (id, transceiver) in self.rtp_transceivers.iter_mut().enumerate() {
2101            if !transceiver.stopped()
2102                && transceiver.kind() == track.kind()
2103                && transceiver.sender().is_none()
2104            {
2105                let mut sender =
2106                    RTCRtpSenderInternal::new(track.kind(), track, vec![], send_encodings);
2107
2108                if transceiver.get_codec_preferences().is_empty() && !codec_preferences.is_empty() {
2109                    transceiver.set_codec_preferences(codec_preferences, &self.media_engine)?;
2110                }
2111
2112                sender.set_codec_preferences(transceiver.get_codec_preferences().to_vec());
2113
2114                transceiver.sender_mut().replace(sender);
2115
2116                transceiver.set_direction(RTCRtpTransceiverDirection::from_send_recv(
2117                    true,
2118                    transceiver.direction().has_recv(),
2119                ));
2120
2121                self.trigger_negotiation_needed();
2122                return Ok(RTCRtpSenderId(id));
2123            }
2124        }
2125
2126        let mut transceiver = self.new_transceiver_from_track(
2127            track,
2128            RTCRtpTransceiverInit {
2129                direction: RTCRtpTransceiverDirection::Sendrecv,
2130                streams: vec![],
2131                send_encodings,
2132            },
2133        )?;
2134        if !codec_preferences.is_empty() {
2135            transceiver.set_codec_preferences(codec_preferences, &self.media_engine)?;
2136        }
2137        Ok(RTCRtpSenderId(self.add_rtp_transceiver(transceiver)))
2138    }
2139
2140    /// Removes a track from the peer connection.
2141    ///
2142    /// This method stops an `RTCRtpSender` from sending media and marks its transceiver
2143    /// as no longer sending. This will trigger renegotiation.
2144    ///
2145    /// # Parameters
2146    ///
2147    /// * `sender_id` - The ID of the `RTCRtpSender` to remove.
2148    ///
2149    /// # Errors
2150    ///
2151    /// Returns an error if:
2152    /// - The peer connection is closed
2153    /// - The sender ID is invalid
2154    ///
2155    /// # Specification
2156    ///
2157    /// See [removeTrack](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-removetrack)
2158    pub fn remove_track(&mut self, sender_id: RTCRtpSenderId) -> Result<()> {
2159        if self.peer_connection_state == RTCPeerConnectionState::Closed {
2160            return Err(Error::ErrConnectionClosed);
2161        }
2162
2163        if sender_id.0 >= self.rtp_transceivers.len() {
2164            return Err(Error::ErrRTPSenderNotExisted);
2165        }
2166
2167        // This also happens in `set_sending_track` but we need to make sure we do this
2168        // before we call sender.stop to avoid a race condition when removing tracks and
2169        // generating offers.
2170        let has_recv = self.rtp_transceivers[sender_id.0].direction().has_recv();
2171        self.rtp_transceivers[sender_id.0]
2172            .set_direction(RTCRtpTransceiverDirection::from_send_recv(false, has_recv));
2173
2174        if let Some(sender) = self.rtp_transceivers[sender_id.0].sender_mut()
2175            && sender
2176                .stop(&self.media_engine, &mut self.interceptor)
2177                .is_ok()
2178        {
2179            self.trigger_negotiation_needed();
2180        }
2181
2182        self.rtp_transceivers[sender_id.0].sender_mut().take();
2183
2184        Ok(())
2185    }
2186
2187    /// Creates a new `RTCRtpTransceiver` and adds it to the set of transceivers.
2188    ///
2189    /// This method creates a transceiver associated with the given track, which can be
2190    /// configured to send, receive, or both.
2191    ///
2192    /// # Parameters
2193    ///
2194    /// * `track` - The media stream track to associate with the transceiver.
2195    /// * `init` - Optional initialization parameters for the transceiver.
2196    ///
2197    /// # Returns
2198    ///
2199    /// Returns the ID of the created transceiver.
2200    ///
2201    /// # Errors
2202    ///
2203    /// Returns an error if the peer connection is closed.
2204    ///
2205    /// # Specification
2206    ///
2207    /// See [addTransceiver](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtransceiver)
2208    pub fn add_transceiver_from_track(
2209        &mut self,
2210        track: MediaStreamTrack,
2211        init: Option<RTCRtpTransceiverInit>,
2212    ) -> Result<RTCRtpTransceiverId> {
2213        if self.peer_connection_state == RTCPeerConnectionState::Closed {
2214            return Err(Error::ErrConnectionClosed);
2215        }
2216
2217        if let Some(init) = init.as_ref()
2218            && !init.direction.has_send()
2219        {
2220            return Err(Error::ErrInvalidDirection);
2221        }
2222
2223        let mut init = if let Some(init) = init {
2224            init
2225        } else {
2226            RTCRtpTransceiverInit {
2227                direction: RTCRtpTransceiverDirection::Sendrecv,
2228                streams: vec![],
2229                send_encodings: vec![],
2230            }
2231        };
2232
2233        let send_encodings = if init.send_encodings.is_empty() {
2234            self.send_encodings_from_track(&track)
2235        } else {
2236            init.send_encodings.clone()
2237        };
2238        let (track, send_encodings, codec_preferences) =
2239            self.normalize_sender_track(track, send_encodings)?;
2240        init.send_encodings = send_encodings;
2241
2242        let mut transceiver = self.new_transceiver_from_track(track, init)?;
2243        if !codec_preferences.is_empty() {
2244            transceiver.set_codec_preferences(codec_preferences, &self.media_engine)?;
2245        }
2246
2247        Ok(self.add_rtp_transceiver(transceiver))
2248    }
2249
2250    /// Creates a new transceiver for the given media kind and adds it to the set of
2251    /// transceivers.
2252    ///
2253    /// # Parameters
2254    ///
2255    /// - `kind`: Audio or video.
2256    /// - `init`: Optional direction and encoding parameters; defaults are used when `None`.
2257    ///
2258    /// # Errors
2259    ///
2260    /// Returns an error if:
2261    /// - The peer connection is closed (`ErrConnectionClosed`)
2262    /// - The requested direction is not supported for a transceiver created this way
2263    /// - No codec is registered in the media engine for `kind`
2264    ///
2265    /// # Specification
2266    ///
2267    /// See [addTransceiver](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtransceiver)
2268    pub fn add_transceiver_from_kind(
2269        &mut self,
2270        kind: RtpCodecKind,
2271        init: Option<RTCRtpTransceiverInit>,
2272    ) -> Result<RTCRtpTransceiverId> {
2273        if self.peer_connection_state == RTCPeerConnectionState::Closed {
2274            return Err(Error::ErrConnectionClosed);
2275        }
2276
2277        let init = if let Some(init) = init {
2278            if init.direction.has_send() && init.send_encodings.is_empty() {
2279                return Err(Error::ErrInvalidDirection);
2280            }
2281
2282            init
2283        } else {
2284            RTCRtpTransceiverInit {
2285                direction: RTCRtpTransceiverDirection::Recvonly,
2286                streams: vec![],
2287                send_encodings: vec![],
2288            }
2289        };
2290
2291        let transceiver = match init.direction {
2292            RTCRtpTransceiverDirection::Sendonly | RTCRtpTransceiverDirection::Sendrecv => {
2293                let mut init = init;
2294                let track = MediaStreamTrack::new(
2295                    math_rand_alpha(16), // MediaStreamId
2296                    math_rand_alpha(16), // MediaStreamTrackId
2297                    math_rand_alpha(16), // Label
2298                    kind,
2299                    init.send_encodings.clone(),
2300                );
2301                let (track, send_encodings, codec_preferences) =
2302                    self.normalize_sender_track(track, init.send_encodings)?;
2303                init.send_encodings = send_encodings;
2304
2305                let mut transceiver = self.new_transceiver_from_track(track, init)?;
2306                if !codec_preferences.is_empty() {
2307                    transceiver.set_codec_preferences(codec_preferences, &self.media_engine)?;
2308                }
2309                transceiver
2310            }
2311            RTCRtpTransceiverDirection::Recvonly => {
2312                RTCRtpTransceiverInternal::new(kind, None, init)
2313            }
2314            _ => return Err(Error::ErrPeerConnAddTransceiverFromKindSupport),
2315        };
2316
2317        Ok(self.add_rtp_transceiver(transceiver))
2318    }
2319
2320    /// Returns a handle to the [`RTCDataChannel`] with the given id, or `None` if no such
2321    /// channel exists on this peer connection.
2322    ///
2323    /// [`RTCDataChannel`]: crate::data_channel::RTCDataChannel
2324    pub fn data_channel(&mut self, id: RTCDataChannelId) -> Option<RTCDataChannel<'_>> {
2325        if self.data_channels.contains(&id) {
2326            Some(RTCDataChannel {
2327                id,
2328                peer_connection: self,
2329            })
2330        } else {
2331            None
2332        }
2333    }
2334
2335    /// The SCTP transport over which data channels are carried.
2336    ///
2337    /// `None` until SCTP has been negotiated — that is, until a description establishing an
2338    /// SCTP association has been applied by both sides. A connection carrying only media never
2339    /// has one.
2340    ///
2341    /// This is the **only** transport accessor on `RTCPeerConnection`, matching the W3C
2342    /// interface, which exposes `sctp` and nothing else. The DTLS and ICE transports are
2343    /// reached by walking from here (or from a sender or receiver):
2344    ///
2345    /// ```text
2346    /// pc.sctp()?.transport().ice_transport()
2347    /// ```
2348    ///
2349    /// ## Specifications
2350    ///
2351    /// * [W3C](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-sctp)
2352    pub fn sctp(&self) -> Option<RTCSctpTransport<'_>> {
2353        if self.sctp_transport().is_started {
2354            Some(RTCSctpTransport {
2355                peer_connection: self,
2356            })
2357        } else {
2358            None
2359        }
2360    }
2361
2362    /// Returns a handle to the [`RTCRtpSender`] with the given id, or `None` if no such
2363    /// sender exists on this peer connection.
2364    ///
2365    /// [`RTCRtpSender`]: crate::rtp_transceiver::rtp_sender::RTCRtpSender
2366    pub fn rtp_sender(&mut self, id: RTCRtpSenderId) -> Option<RTCRtpSender<'_>> {
2367        if id.0 < self.rtp_transceivers.len()
2368            && self.rtp_transceivers[id.0].direction().has_send()
2369            && self.rtp_transceivers[id.0].sender().is_some()
2370        {
2371            Some(RTCRtpSender {
2372                id,
2373                peer_connection: self,
2374            })
2375        } else {
2376            None
2377        }
2378    }
2379
2380    /// Returns a handle to the [`RTCRtpReceiver`] with the given id, or `None` if no such
2381    /// receiver exists on this peer connection.
2382    ///
2383    /// [`RTCRtpReceiver`]: crate::rtp_transceiver::rtp_receiver::RTCRtpReceiver
2384    pub fn rtp_receiver(&mut self, id: RTCRtpReceiverId) -> Option<RTCRtpReceiver<'_>> {
2385        if id.0 < self.rtp_transceivers.len()
2386            && self.rtp_transceivers[id.0].direction().has_recv()
2387            && self.rtp_transceivers[id.0].receiver().is_some()
2388        {
2389            Some(RTCRtpReceiver {
2390                id,
2391                peer_connection: self,
2392            })
2393        } else {
2394            None
2395        }
2396    }
2397
2398    /// Returns a handle to the [`RTCRtpTransceiver`] with the given id, or `None` if no such
2399    /// transceiver exists on this peer connection.
2400    ///
2401    /// [`RTCRtpTransceiver`]: crate::rtp_transceiver::RTCRtpTransceiver
2402    pub fn rtp_transceiver(&mut self, id: RTCRtpTransceiverId) -> Option<RTCRtpTransceiver<'_>> {
2403        if id < self.rtp_transceivers.len() {
2404            Some(RTCRtpTransceiver {
2405                id,
2406                peer_connection: self,
2407            })
2408        } else {
2409            None
2410        }
2411    }
2412
2413    /// Returns a snapshot of accumulated statistics.
2414    ///
2415    /// This method creates an immutable snapshot of WebRTC statistics
2416    /// at the given timestamp. When `selector` is `StatsSelector::None`,
2417    /// the returned `RTCStatsReport` contains statistics for all aspects
2418    /// of the peer connection. When a sender or receiver is specified,
2419    /// only statistics relevant to that sender/receiver are included.
2420    ///
2421    /// # Statistics included by selector
2422    ///
2423    /// - `StatsSelector::None` - All statistics for the entire connection
2424    /// - `StatsSelector::Sender(id)` - Outbound RTP streams for the sender
2425    ///   and all referenced stats (transport, codec, remote inbound, etc.)
2426    /// - `StatsSelector::Receiver(id)` - Inbound RTP streams for the receiver
2427    ///   and all referenced stats (transport, codec, remote outbound, etc.)
2428    ///
2429    /// # Parameters
2430    ///
2431    /// * `now` - The timestamp to use for all stats in the report. This is
2432    ///   passed explicitly to support deterministic testing.
2433    /// * `selector` - Controls which statistics are included in the report.
2434    ///
2435    /// # Returns
2436    ///
2437    /// An `RTCStatsReport` containing snapshots of the selected statistics.
2438    ///
2439    /// # Examples
2440    ///
2441    /// ```no_run
2442    /// use std::time::Instant;
2443    /// use rtc::peer_connection::RTCPeerConnectionBuilder;
2444    /// use rtc::statistics::StatsSelector;
2445    ///
2446    /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
2447    /// let mut pc = RTCPeerConnectionBuilder::new().build(Instant::now())?;
2448    ///
2449    /// // Get all stats
2450    /// let report = pc.get_stats(Instant::now(), StatsSelector::None);
2451    ///
2452    /// // Access peer connection stats
2453    /// if let Some(pc_stats) = report.peer_connection() {
2454    ///     println!("Data channels opened: {}", pc_stats.data_channels_opened);
2455    /// }
2456    ///
2457    /// // Iterate over inbound RTP streams
2458    /// for stream in report.inbound_rtp_streams() {
2459    ///     println!("SSRC {}: {} packets received", stream.received_rtp_stream_stats.rtp_stream_stats.ssrc, stream.received_rtp_stream_stats.packets_received);
2460    /// }
2461    /// # Ok(())
2462    /// # }
2463    /// ```
2464    ///
2465    /// # Specification
2466    ///
2467    /// See [getStats](https://www.w3.org/TR/webrtc/#widl-RTCPeerConnection-getStats-Promise-RTCStatsReport--MediaStreamTrack-selector) and
2468    /// [The stats selection algorithm](https://www.w3.org/TR/webrtc/#the-stats-selection-algorithm)
2469    pub fn get_stats(&mut self, now: Instant, selector: StatsSelector) -> RTCStatsReport {
2470        // Update ICE agent stats before taking snapshot
2471        self.update_ice_agent_stats(now);
2472        // Update codec stats from transceivers before taking snapshot
2473        self.update_codec_stats();
2474        self.pipeline_context
2475            .stats
2476            .snapshot_with_selector(now, selector)
2477    }
2478}
2479
2480#[cfg(test)]
2481mod tests {
2482    use super::*;
2483    use crate::data_channel::state::RTCDataChannelState;
2484    use crate::peer_connection::configuration::setting_engine::SctpMaxMessageSize;
2485    use crate::peer_connection::configuration::setting_engine::SettingEngineBuilder;
2486    use crate::peer_connection::transport::RTCIceComponent;
2487    use crate::peer_connection::transport::dtls::state::RTCDtlsTransportState;
2488    use sctp::AssociationHandle;
2489
2490    #[test]
2491    fn with_sctp_receive_buffer_size_sets_and_clamps() {
2492        let setting_engine = SettingEngineBuilder::new()
2493            .with_sctp_max_receive_buffer_size(200_000)
2494            .build();
2495
2496        let builder = RTCPeerConnectionBuilder::new().with_setting_engine(setting_engine);
2497        assert_eq!(
2498            builder.setting_engine.sctp_max_receive_buffer_size,
2499            Some(200_000)
2500        );
2501
2502        // Values below the RFC 9260 §3.3.2 floor (1500 bytes), including 0, are clamped up so
2503        // they cannot break the SCTP handshake.
2504        for input in [0u32, 500, 1499] {
2505            let setting_engine = SettingEngineBuilder::new()
2506                .with_sctp_max_receive_buffer_size(input)
2507                .build();
2508            let builder = RTCPeerConnectionBuilder::new().with_setting_engine(setting_engine);
2509            assert_eq!(
2510                builder.setting_engine.sctp_max_receive_buffer_size,
2511                Some(1500),
2512                "input {input} should clamp up to the 1500-byte floor"
2513            );
2514        }
2515    }
2516
2517    // The graph the spec exposes: `pc.sctp` is the only way in, and the rest is walked.
2518    #[test]
2519    fn sctp_is_none_until_sctp_is_negotiated() {
2520        let pc = RTCPeerConnectionBuilder::new()
2521            .build(Instant::now())
2522            .unwrap();
2523        assert!(
2524            pc.sctp().is_none(),
2525            "nothing has been negotiated, so there is no SCTP transport to expose"
2526        );
2527    }
2528
2529    #[test]
2530    fn the_transport_graph_is_walkable_and_ids_identify() {
2531        let mut pc = RTCPeerConnectionBuilder::new()
2532            .build(Instant::now())
2533            .unwrap();
2534        // `start()` is what negotiation calls once both descriptions carry an m=application
2535        // section; it is the predicate `sctp()` keys off.
2536        pc.sctp_transport_mut()
2537            .start(
2538                RTCDtlsRole::Client,
2539                crate::peer_connection::transport::sctp::capabilities::SCTPTransportCapabilities {
2540                    max_message_size: 0,
2541                },
2542                5000,
2543                5000,
2544            )
2545            .expect("start");
2546
2547        let sctp = pc.sctp().expect("SCTP is negotiated");
2548        let dtls = sctp.transport();
2549        let ice = dtls.ice_transport();
2550
2551        // Three transports, three identities.
2552        assert_ne!(sctp.id(), dtls.id());
2553        assert_ne!(dtls.id(), ice.id());
2554        assert_ne!(sctp.id(), ice.id());
2555
2556        // Ids are stored, not minted per call: walking twice yields the same identity.
2557        let dtls_again = pc.sctp().unwrap().transport();
2558        assert_eq!(dtls.id(), dtls_again.id());
2559        assert_eq!(ice.id(), dtls_again.ice_transport().id());
2560
2561        // The default configuration caps messages at 64 KiB and the peer advertised no limit,
2562        // so the negotiated value is this endpoint's cap.
2563        assert_eq!(Some(65536), sctp.max_message_size());
2564        // No association yet, so no negotiated stream count.
2565        assert_eq!(None, sctp.max_channels());
2566        assert_eq!(RTCIceComponent::Rtp, ice.component());
2567    }
2568
2569    // W3C types `maxMessageSize` `unrestricted double` so that an implementation with no limit
2570    // can report +Infinity. This one always has a limit — the working buffer is a real
2571    // allocation — so a configuration naming no limit resolves to the implementation ceiling,
2572    // and the value reported is the value enforced.
2573    #[test]
2574    fn max_message_size_with_no_configured_limit_reports_the_ceiling() {
2575        let setting_engine = SettingEngineBuilder::new()
2576            .with_sctp_max_message_size(SctpMaxMessageSize::Bounded(0))
2577            .build();
2578        let mut pc = RTCPeerConnectionBuilder::new()
2579            .with_setting_engine(setting_engine)
2580            .build(Instant::now())
2581            .unwrap();
2582        pc.sctp_transport_mut()
2583            .start(
2584                RTCDtlsRole::Client,
2585                crate::peer_connection::transport::sctp::capabilities::SCTPTransportCapabilities {
2586                    max_message_size: 0,
2587                },
2588                5000,
2589                5000,
2590            )
2591            .expect("start");
2592
2593        assert_eq!(
2594            Some(SctpMaxMessageSize::MAX_MESSAGE_SIZE),
2595            pc.sctp().expect("negotiated").max_message_size()
2596        );
2597    }
2598
2599    // `RTCRtpSender.transport` / `RTCRtpReceiver.transport` come from the per-object
2600    // `[[SenderTransport]]` / `[[ReceiverTransport]]` slots, filled when the transceiver is
2601    // associated by negotiation. Before that the spec reports null.
2602    #[test]
2603    fn sender_and_receiver_transport_are_none_until_the_transceiver_is_associated() {
2604        let mut pc = media_pc();
2605        // `add_track` creates a sendrecv transceiver, so this one has both a sender and a
2606        // receiver while still being unassociated.
2607        let track = MediaStreamTrack::new(
2608            "stream".to_owned(),
2609            "track".to_owned(),
2610            "label".to_owned(),
2611            RtpCodecKind::Audio,
2612            vec![],
2613        );
2614        let sender_id = pc.add_track(track).expect("add track");
2615        let receiver_id = RTCRtpReceiverId::from(sender_id.0);
2616
2617        assert!(pc.rtp_transceivers[sender_id.0].mid().is_none());
2618        assert!(
2619            pc.rtp_sender(sender_id)
2620                .expect("sender")
2621                .transport()
2622                .is_none(),
2623            "an unassociated sender has a null transport"
2624        );
2625        assert!(
2626            pc.rtp_receiver(receiver_id)
2627                .expect("receiver")
2628                .transport()
2629                .is_none(),
2630            "an unassociated receiver has a null transport"
2631        );
2632
2633        // Applying a local offer associates the transceiver. This is the offerer's window: a mid
2634        // exists, but no answer has arrived so DTLS has not started — and a browser reports a
2635        // transport here, which is why the predicate is association rather than "DTLS is up".
2636        let offer = pc.create_offer(None).expect("create offer");
2637        pc.set_local_description(Instant::now(), offer)
2638            .expect("set local description");
2639        assert!(pc.rtp_transceivers[sender_id.0].mid().is_some());
2640        assert!(
2641            !pc.dtls_transport().is_started(),
2642            "no answer yet, so DTLS has not been brought up"
2643        );
2644
2645        let sender_transport_id = pc
2646            .rtp_sender(sender_id)
2647            .expect("sender")
2648            .transport()
2649            .expect("an associated sender has a transport")
2650            .id();
2651        let receiver_transport_id = pc
2652            .rtp_receiver(receiver_id)
2653            .expect("receiver")
2654            .transport()
2655            .expect("an associated receiver has a transport")
2656            .id();
2657
2658        // Under bundling both directions share one DTLS transport, as the spec says...
2659        assert_eq!(sender_transport_id, receiver_transport_id);
2660        // ...and it is the same transport the rest of the graph names.
2661        assert_eq!(sender_transport_id, pc.dtls_transport().id);
2662        // Its state is `New` until the handshake begins: a transport that exists but has not
2663        // connected, exactly as a browser reports in this window.
2664        assert_eq!(RTCDtlsTransportState::New, pc.dtls_transport().state());
2665    }
2666
2667    // `with_discard_local_candidates_during_ice_restart` has to reach `apply_restart`, not just
2668    // land in the struct. Applying a restart with it set must empty the local candidates the
2669    // agent had gathered; with it unset they survive.
2670    //
2671    // This is the setting that makes a socket-rebinding ICE restart work (webrtc#868): retained
2672    // candidates name addresses nothing is bound to any more, so checks written for them go
2673    // nowhere and the restarted generation never leaves `Checking`.
2674    #[test]
2675    fn discard_local_candidates_during_ice_restart_reaches_apply_restart() {
2676        fn restart_with(discard: bool) -> usize {
2677            let setting_engine = SettingEngineBuilder::new()
2678                .with_discard_local_candidates_during_ice_restart(discard)
2679                .build();
2680            let mut pc = RTCPeerConnectionBuilder::new()
2681                .with_setting_engine(setting_engine)
2682                .build(Instant::now())
2683                .unwrap();
2684
2685            // Gather one host candidate so there is something to keep or drop.
2686            pc.add_local_candidate(RTCIceCandidateInit {
2687                candidate: "candidate:1 1 udp 2130706431 127.0.0.1 5000 typ host".to_owned(),
2688                ..Default::default()
2689            })
2690            .expect("add local candidate");
2691            assert_eq!(
2692                1,
2693                pc.ice_transport().get_local_candidates().unwrap().len(),
2694                "precondition: the agent holds the gathered candidate"
2695            );
2696
2697            pc.ice_transport_mut()
2698                .generate_restart_credentials(
2699                    "newufrag".to_owned(),
2700                    "newpasswordlongenough".to_owned(),
2701                )
2702                .expect("stage restart");
2703            pc.apply_ice_restart(Instant::now())
2704                .expect("apply ice restart");
2705
2706            pc.ice_transport().get_local_candidates().unwrap().len()
2707        }
2708
2709        assert_eq!(
2710            0,
2711            restart_with(true),
2712            "with discard enabled the stale generation's candidates are dropped"
2713        );
2714        assert_eq!(
2715            1,
2716            restart_with(false),
2717            "the default keeps them, which is the pre-existing behaviour"
2718        );
2719    }
2720
2721    // Two connections must never report the same transport as each other's. This is the case a
2722    // per-connection counter or a small-integer scheme gets wrong.
2723    #[test]
2724    fn transports_of_two_peer_connections_are_never_equal() {
2725        let mut ids = vec![];
2726        for _ in 0..2 {
2727            let mut pc = RTCPeerConnectionBuilder::new()
2728                .build(Instant::now())
2729                .unwrap();
2730            pc.sctp_transport_mut()
2731                .start(
2732                    RTCDtlsRole::Client,
2733                    crate::peer_connection::transport::sctp::capabilities::SCTPTransportCapabilities {
2734                        max_message_size: 0,
2735                    },
2736                    5000,
2737                    5000,
2738                )
2739                .expect("start");
2740            let sctp = pc.sctp().expect("SCTP is negotiated");
2741            ids.push((
2742                sctp.id(),
2743                sctp.transport().id(),
2744                sctp.transport().ice_transport().id(),
2745            ));
2746        }
2747
2748        let (a_sctp, a_dtls, a_ice) = ids[0];
2749        let (b_sctp, b_dtls, b_ice) = ids[1];
2750        assert_ne!(a_sctp, b_sctp);
2751        assert_ne!(a_dtls, b_dtls);
2752        assert_ne!(a_ice, b_ice);
2753    }
2754
2755    #[test]
2756    fn create_data_channel_dials_immediately_when_sctp_association_present() {
2757        let mut pc = RTCPeerConnectionBuilder::new()
2758            .build(Instant::now())
2759            .unwrap();
2760
2761        // Simulate an SCTP association so create_data_channel sees a transport
2762        // that is ready to open streams, and the resolved DTLS role that always accompanies
2763        // one in practice — the association is built by `SCTPTransport::start`, which runs
2764        // after `prepare_transport` has settled the role. Without it there is no correct
2765        // stream-id parity to pick and the channel would rightly defer (RFC 8832 §6).
2766        pc.dtls_transport_mut().dtls_role = RTCDtlsRole::Client;
2767        pc.sctp_transport_mut()
2768            .sctp_associations
2769            .insert(AssociationHandle(0), sctp::Association::default());
2770
2771        let _dc = pc.create_data_channel("test", None).unwrap();
2772
2773        let internal = pc
2774            .data_channels
2775            .values()
2776            .next()
2777            .expect("data channel must be stored internally");
2778        assert!(internal.data_channel.is_some());
2779        assert_eq!(
2780            internal.ready_state,
2781            RTCDataChannelState::Connecting,
2782            "a dialed in-band channel stays Connecting until its DATA_CHANNEL_ACK arrives"
2783        );
2784        assert_eq!(
2785            internal.stream_id,
2786            Some(0),
2787            "the DTLS client must take an even stream id (RFC 8832 §6)"
2788        );
2789    }
2790
2791    /// The mirror of the case above: with no resolved DTLS role there is no correct parity, so
2792    /// the channel is created without a stream id and waits for the connected procedure rather
2793    /// than being dialed or rejected.
2794    #[test]
2795    fn create_data_channel_defers_stream_id_when_dtls_role_unresolved() {
2796        let mut pc = RTCPeerConnectionBuilder::new()
2797            .build(Instant::now())
2798            .unwrap();
2799        pc.sctp_transport_mut()
2800            .sctp_associations
2801            .insert(AssociationHandle(0), sctp::Association::default());
2802
2803        let dc = pc.create_data_channel("test", None).unwrap();
2804        let id = dc.id();
2805
2806        let internal = pc.data_channels.get(&id).unwrap();
2807        assert_eq!(internal.stream_id, None, "no parity is knowable yet");
2808        assert!(
2809            internal.data_channel.is_none(),
2810            "a channel with no stream id cannot be dialed"
2811        );
2812    }
2813
2814    // ---- Rollback (RFC 9429, Section 5.7) ----
2815
2816    use crate::peer_connection::configuration::media_engine::MediaEngine;
2817    use crate::rtp_transceiver::RTCRtpTransceiverInit;
2818
2819    fn media_pc() -> RTCPeerConnection {
2820        let mut me = MediaEngine::default();
2821        me.register_default_codecs().unwrap();
2822        RTCPeerConnectionBuilder::new()
2823            .with_media_engine(me)
2824            .build(Instant::now())
2825            .unwrap()
2826    }
2827
2828    /// Builds an audio+video offer from a freshly-created peer connection.
2829    fn audio_video_offer() -> RTCSessionDescription {
2830        let mut offerer = media_pc();
2831        offerer
2832            .add_transceiver_from_kind(
2833                RtpCodecKind::Audio,
2834                Some(RTCRtpTransceiverInit {
2835                    direction: RTCRtpTransceiverDirection::Recvonly,
2836                    streams: vec![],
2837                    send_encodings: vec![],
2838                }),
2839            )
2840            .unwrap();
2841        offerer
2842            .add_transceiver_from_kind(
2843                RtpCodecKind::Video,
2844                Some(RTCRtpTransceiverInit {
2845                    direction: RTCRtpTransceiverDirection::Recvonly,
2846                    streams: vec![],
2847                    send_encodings: vec![],
2848                }),
2849            )
2850            .unwrap();
2851        offerer.create_offer(None).unwrap()
2852    }
2853
2854    fn rollback() -> RTCSessionDescription {
2855        RTCSessionDescription {
2856            sdp_type: RTCSdpType::Rollback,
2857            sdp: String::new(),
2858            parsed: None,
2859        }
2860    }
2861
2862    #[test]
2863    fn set_remote_rollback_removes_transceivers_created_by_remote_offer() {
2864        let offer = audio_video_offer();
2865
2866        let mut pc = media_pc();
2867        pc.set_remote_description(Instant::now(), offer).unwrap();
2868
2869        // Applying the remote offer implicitly creates two transceivers, each associated
2870        // with an "m=" section.
2871        assert_eq!(pc.rtp_transceivers.len(), 2);
2872        assert_eq!(pc.signaling_state, RTCSignalingState::HaveRemoteOffer);
2873        assert!(pc.rtp_transceivers.iter().all(|t| t.mid().is_some()));
2874
2875        // Rolling back the offer must stop and remove those transceivers and return to stable.
2876        pc.set_remote_description(Instant::now(), rollback())
2877            .unwrap();
2878
2879        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
2880        assert!(
2881            pc.rtp_transceivers.is_empty(),
2882            "transceivers created by a rolled-back remote offer must be removed"
2883        );
2884    }
2885
2886    #[test]
2887    fn set_local_rollback_disassociates_but_keeps_app_created_transceivers() {
2888        // A locally-initiated offer: the transceivers are created by the application, so
2889        // rolling back the local offer via set_local_description must disassociate them
2890        // (clear their mids) but must NOT remove them. This exercises the same rollback
2891        // cleanup path as set_remote_description, per RFC 9429 Section 5.7.
2892        let mut pc = media_pc();
2893        pc.add_transceiver_from_kind(
2894            RtpCodecKind::Audio,
2895            Some(RTCRtpTransceiverInit {
2896                direction: RTCRtpTransceiverDirection::Recvonly,
2897                streams: vec![],
2898                send_encodings: vec![],
2899            }),
2900        )
2901        .unwrap();
2902
2903        let offer = pc.create_offer(None).unwrap();
2904        pc.set_local_description(Instant::now(), offer).unwrap();
2905        assert_eq!(pc.signaling_state, RTCSignalingState::HaveLocalOffer);
2906        assert_eq!(pc.rtp_transceivers.len(), 1);
2907        assert!(pc.rtp_transceivers[0].mid().is_some());
2908
2909        pc.set_local_description(Instant::now(), rollback())
2910            .unwrap();
2911
2912        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
2913        assert_eq!(
2914            pc.rtp_transceivers.len(),
2915            1,
2916            "application-created transceivers must not be removed by rollback"
2917        );
2918        assert!(
2919            pc.rtp_transceivers[0].mid().is_none(),
2920            "rolled-back transceiver must be disassociated from its m= section"
2921        );
2922    }
2923
2924    #[test]
2925    fn rollback_keeps_transceiver_with_track_attached_via_add_track() {
2926        let offer = audio_video_offer();
2927
2928        let mut pc = media_pc();
2929        pc.set_remote_description(Instant::now(), offer).unwrap();
2930        assert_eq!(pc.rtp_transceivers.len(), 2);
2931
2932        // Attach a local track. add_track reuses the sender-less audio transceiver that was
2933        // created by the remote offer, giving it a sender.
2934        let track = MediaStreamTrack::new(
2935            "stream".to_owned(),
2936            "track".to_owned(),
2937            "label".to_owned(),
2938            RtpCodecKind::Audio,
2939            vec![],
2940        );
2941        pc.add_track(track).unwrap();
2942
2943        pc.set_remote_description(Instant::now(), rollback())
2944            .unwrap();
2945
2946        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
2947        // The video transceiver (no track) is removed; the audio transceiver with the attached
2948        // track is kept but disassociated (mid cleared) so a future offer can re-add it.
2949        assert_eq!(
2950            pc.rtp_transceivers.len(),
2951            1,
2952            "transceiver with a track attached via add_track must not be removed"
2953        );
2954        let kept = &pc.rtp_transceivers[0];
2955        assert_eq!(kept.kind(), RtpCodecKind::Audio);
2956        assert!(kept.sender().is_some());
2957        assert!(
2958            kept.mid().is_none(),
2959            "kept transceiver must be disassociated from its m= section on rollback"
2960        );
2961    }
2962
2963    #[test]
2964    fn rollback_keeps_transceiver_negotiated_by_a_previous_exchange() {
2965        // First, complete a full offer/answer so an incoming transceiver becomes part of the
2966        // stable state (its mid is recorded in the current remote description).
2967        let offer = audio_video_offer();
2968        let mut pc = media_pc();
2969        pc.set_remote_description(Instant::now(), offer).unwrap();
2970        assert_eq!(pc.rtp_transceivers.len(), 2);
2971        let answer = pc.create_answer(None).unwrap();
2972        pc.set_local_description(Instant::now(), answer).unwrap();
2973        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
2974        let negotiated_mids: Vec<_> = pc
2975            .rtp_transceivers
2976            .iter()
2977            .map(|t| t.mid().clone())
2978            .collect();
2979        assert!(negotiated_mids.iter().all(|m| m.is_some()));
2980
2981        // Now a renegotiation arrives and is rolled back. The transceivers negotiated by the
2982        // FIRST exchange must survive with their mids intact — rollback only undoes the pending
2983        // (second) transaction, not committed state.
2984        let reoffer = audio_video_offer();
2985        pc.set_remote_description(Instant::now(), reoffer).unwrap();
2986        assert_eq!(pc.signaling_state, RTCSignalingState::HaveRemoteOffer);
2987
2988        pc.set_remote_description(Instant::now(), rollback())
2989            .unwrap();
2990
2991        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
2992        assert_eq!(
2993            pc.rtp_transceivers.len(),
2994            2,
2995            "previously-negotiated transceivers must not be removed by a renegotiation rollback"
2996        );
2997        let mids_after: Vec<_> = pc
2998            .rtp_transceivers
2999            .iter()
3000            .map(|t| t.mid().clone())
3001            .collect();
3002        assert_eq!(
3003            mids_after, negotiated_mids,
3004            "previously-negotiated transceivers must keep their mid across rollback"
3005        );
3006    }
3007
3008    #[test]
3009    fn add_track_then_rollback_remote_offer_then_create_offer_includes_track() {
3010        // RFC 9429, Section 5.7: "an application may call addTrack, then call
3011        // setRemoteDescription with an offer, then roll back that offer, then call createOffer
3012        // and have an "m=" section for the added track appear in the generated offer."
3013        let mut pc = media_pc();
3014
3015        // 1. addTrack — creates a local sendrecv audio transceiver with a sender.
3016        let track = MediaStreamTrack::new(
3017            "stream".to_owned(),
3018            "track".to_owned(),
3019            "label".to_owned(),
3020            RtpCodecKind::Audio,
3021            vec![],
3022        );
3023        pc.add_track(track).unwrap();
3024        assert_eq!(pc.rtp_transceivers.len(), 1);
3025
3026        // 2. setRemoteDescription with a remote (video-only) offer. This reuses no existing
3027        //    transceiver (different kind), so it creates a second, remote-originated one.
3028        let mut video_offerer = media_pc();
3029        video_offerer
3030            .add_transceiver_from_kind(
3031                RtpCodecKind::Video,
3032                Some(RTCRtpTransceiverInit {
3033                    direction: RTCRtpTransceiverDirection::Recvonly,
3034                    streams: vec![],
3035                    send_encodings: vec![],
3036                }),
3037            )
3038            .unwrap();
3039        let remote_offer = video_offerer.create_offer(None).unwrap();
3040        pc.set_remote_description(Instant::now(), remote_offer)
3041            .unwrap();
3042        assert_eq!(pc.signaling_state, RTCSignalingState::HaveRemoteOffer);
3043        // The audio transceiver (with our track) got associated with the offer's m= section.
3044        assert!(
3045            pc.rtp_transceivers
3046                .iter()
3047                .any(|t| t.kind() == RtpCodecKind::Audio && t.sender().is_some())
3048        );
3049
3050        // 3. Roll back that offer.
3051        pc.set_remote_description(Instant::now(), rollback())
3052            .unwrap();
3053        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
3054
3055        // The audio transceiver with the attached track must survive (disassociated), while the
3056        // remote-created video transceiver must be gone.
3057        assert_eq!(
3058            pc.rtp_transceivers.len(),
3059            1,
3060            "the add_track transceiver must survive rollback; the remote one must be removed"
3061        );
3062        let kept = &pc.rtp_transceivers[0];
3063        assert_eq!(kept.kind(), RtpCodecKind::Audio);
3064        assert!(kept.sender().is_some());
3065        assert!(kept.mid().is_none(), "must be disassociated after rollback");
3066
3067        // 4. createOffer — an m= section for the added (audio) track must appear.
3068        let offer = pc.create_offer(None).unwrap();
3069        assert_eq!(
3070            offer.sdp.matches("m=audio").count(),
3071            1,
3072            "createOffer after rollback must emit an m=audio section for the added track"
3073        );
3074        // The transceiver has been re-associated (given a mid) for the new offer.
3075        assert!(pc.rtp_transceivers[0].mid().is_some());
3076    }
3077
3078    #[test]
3079    fn add_track_then_rollback_local_offer_then_answer_remote_still_renegotiates_track() {
3080        // Polite-peer glare scenario (RFC 9429, Section 5.7): the application calls addTrack and
3081        // sends its own offer, then a remote offer arrives (collision). The polite peer rolls
3082        // back its local offer, applies the remote offer, and answers it. The locally-added
3083        // track was never negotiated, so it must still be pending and appear in a subsequent
3084        // offer — mirroring the RFC's addTrack/rollback/createOffer guarantee for the local case.
3085        let mut pc = media_pc();
3086
3087        // 1. addTrack (audio) — local sendrecv audio transceiver with a sender.
3088        let track = MediaStreamTrack::new(
3089            "stream".to_owned(),
3090            "track".to_owned(),
3091            "label".to_owned(),
3092            RtpCodecKind::Audio,
3093            vec![],
3094        );
3095        pc.add_track(track).unwrap();
3096
3097        // 2. set_local_description(our offer) — enters have-local-offer, audio gets a mid.
3098        let local_offer = pc.create_offer(None).unwrap();
3099        pc.set_local_description(Instant::now(), local_offer)
3100            .unwrap();
3101        assert_eq!(pc.signaling_state, RTCSignalingState::HaveLocalOffer);
3102        assert!(pc.rtp_transceivers[0].mid().is_some());
3103
3104        // 3. A remote (video) offer arrives — glare.
3105        let mut video_offerer = media_pc();
3106        video_offerer
3107            .add_transceiver_from_kind(
3108                RtpCodecKind::Video,
3109                Some(RTCRtpTransceiverInit {
3110                    direction: RTCRtpTransceiverDirection::Recvonly,
3111                    streams: vec![],
3112                    send_encodings: vec![],
3113                }),
3114            )
3115            .unwrap();
3116        let remote_offer = video_offerer.create_offer(None).unwrap();
3117
3118        // 4. Roll back our local offer (via set_local_description, the polite-peer path).
3119        pc.set_local_description(Instant::now(), rollback())
3120            .unwrap();
3121        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
3122        // Our audio transceiver survives (has a track/sender) but is disassociated.
3123        assert_eq!(pc.rtp_transceivers.len(), 1);
3124        assert_eq!(pc.rtp_transceivers[0].kind(), RtpCodecKind::Audio);
3125        assert!(pc.rtp_transceivers[0].sender().is_some());
3126        assert!(pc.rtp_transceivers[0].mid().is_none());
3127
3128        // 5. Apply the remote offer, then answer it.
3129        pc.set_remote_description(Instant::now(), remote_offer)
3130            .unwrap();
3131        assert_eq!(pc.signaling_state, RTCSignalingState::HaveRemoteOffer);
3132        let answer = pc.create_answer(None).unwrap();
3133        // The answer only covers the remote's video m= section; our audio track is not yet
3134        // negotiated, so it must NOT appear in the answer.
3135        assert_eq!(answer.sdp.matches("m=video").count(), 1);
3136        assert_eq!(answer.sdp.matches("m=audio").count(), 0);
3137        pc.set_local_description(Instant::now(), answer).unwrap();
3138        assert_eq!(pc.signaling_state, RTCSignalingState::Stable);
3139
3140        // 6. The locally-added track was never negotiated, so a follow-up offer must include an
3141        //    m=audio section for it (alongside the now-negotiated video section).
3142        let followup_offer = pc.create_offer(None).unwrap();
3143        assert_eq!(
3144            followup_offer.sdp.matches("m=audio").count(),
3145            1,
3146            "the added track must appear in the offer generated after rollback + answer"
3147        );
3148        assert_eq!(followup_offer.sdp.matches("m=video").count(), 1);
3149        assert!(
3150            pc.rtp_transceivers
3151                .iter()
3152                .find(|t| t.kind() == RtpCodecKind::Audio)
3153                .unwrap()
3154                .mid()
3155                .is_some(),
3156            "audio transceiver must be re-associated for the follow-up offer"
3157        );
3158    }
3159}