Skip to main content

rustrtc/
config.rs

1use crate::media::depacketizer::{DefaultDepacketizerFactory, DepacketizerFactory};
2use crate::peer_connection::{RtpReceiverInterceptor, RtpSenderInterceptor};
3use serde::{Deserialize, Serialize};
4use std::fmt::{Debug, Formatter};
5use std::sync::Arc;
6
7/// Describes how credentials are conveyed for a given ICE server.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum IceCredentialType {
11    #[default]
12    Password,
13    Oauth,
14}
15
16/// Mirrors the W3C `RTCIceServer` dictionary.
17#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
18pub struct IceServer {
19    pub urls: Vec<String>,
20    pub username: Option<String>,
21    pub credential: Option<String>,
22    #[serde(default)]
23    pub credential_type: IceCredentialType,
24}
25
26impl IceServer {
27    pub fn new<T: Into<Vec<String>>>(urls: T) -> Self {
28        Self {
29            urls: urls.into(),
30            username: None,
31            credential: None,
32            credential_type: IceCredentialType::default(),
33        }
34    }
35
36    pub fn with_credential(
37        mut self,
38        username: impl Into<String>,
39        credential: impl Into<String>,
40    ) -> Self {
41        self.username = Some(username.into());
42        self.credential = Some(credential.into());
43        self
44    }
45
46    pub fn credential_type(mut self, kind: IceCredentialType) -> Self {
47        self.credential_type = kind;
48        self
49    }
50}
51
52impl Default for IceServer {
53    fn default() -> Self {
54        Self::new(Vec::new())
55    }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
59pub enum IceTransportPolicy {
60    #[default]
61    All,
62    Relay,
63}
64
65/// Controls ICE TCP candidate support (RFC 6544).
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
67pub enum IceTcpPolicy {
68    /// Do not gather or use TCP candidates.
69    #[default]
70    Disabled,
71    /// Gather and use TCP candidates (both active and passive).
72    Enabled,
73    /// Only gather and use passive TCP candidates.
74    PassiveOnly,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
78pub enum BundlePolicy {
79    #[default]
80    Balanced,
81    MaxCompat,
82    MaxBundle,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
86pub enum RtcpMuxPolicy {
87    #[default]
88    Require,
89    Negotiate,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
93pub enum TransportMode {
94    #[default]
95    WebRtc,
96    Srtp,
97    Rtp,
98}
99
100/// Strategy for dropping packets when buffer is full.
101#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
102pub enum BufferDropStrategy {
103    #[default]
104    DropNew,
105    DropOldest,
106}
107
108/// Tracks user-supplied certificate material.
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
110pub struct CertificateConfig {
111    pub pem_chain: Vec<String>,
112    pub private_key_pem: Option<String>,
113}
114
115/// Configuration for audio/video codecs and parameters.
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117pub struct AudioCapability {
118    pub payload_type: u8,
119    pub codec_name: String,
120    pub clock_rate: u32,
121    pub channels: u8,
122    pub fmtp: Option<String>,
123    pub rtcp_fbs: Vec<String>,
124}
125
126impl Default for AudioCapability {
127    fn default() -> Self {
128        Self {
129            payload_type: 111,
130            codec_name: "opus".to_string(),
131            clock_rate: 48000,
132            channels: 2,
133            fmtp: Some("minptime=10;useinbandfec=1;stereo=1".to_string()),
134            rtcp_fbs: vec![],
135        }
136    }
137}
138
139impl AudioCapability {
140    pub fn opus() -> Self {
141        Self::default()
142    }
143
144    pub fn pcmu() -> Self {
145        Self {
146            payload_type: 0,
147            codec_name: "PCMU".to_string(),
148            clock_rate: 8000,
149            channels: 1,
150            fmtp: None,
151            rtcp_fbs: vec![],
152        }
153    }
154
155    pub fn pcma() -> Self {
156        Self {
157            payload_type: 8,
158            codec_name: "PCMA".to_string(),
159            clock_rate: 8000,
160            channels: 1,
161            fmtp: None,
162            rtcp_fbs: vec![],
163        }
164    }
165
166    pub fn g722() -> Self {
167        Self {
168            payload_type: 9,
169            codec_name: "G722".to_string(),
170            clock_rate: 8000,
171            channels: 1,
172            fmtp: None,
173            rtcp_fbs: vec![],
174        }
175    }
176
177    pub fn g729() -> Self {
178        Self {
179            payload_type: 18,
180            codec_name: "G729".to_string(),
181            clock_rate: 8000,
182            channels: 1,
183            fmtp: None,
184            rtcp_fbs: vec![],
185        }
186    }
187
188    pub fn telephone_event() -> Self {
189        Self {
190            payload_type: 101,
191            codec_name: "telephone-event".to_string(),
192            clock_rate: 8000,
193            channels: 1,
194            fmtp: Some("0-16".to_string()),
195            rtcp_fbs: vec![],
196        }
197    }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201pub struct VideoCapability {
202    pub payload_type: u8,
203    pub codec_name: String,
204    pub clock_rate: u32,
205    pub fmtp: Option<String>,
206    pub rtcp_fbs: Vec<String>,
207    /// Associated RTX payload type (RFC 4588). When set, SDP offers include
208    /// `a=rtpmap:<pt> rtx/<clock_rate>` and `a=fmtp:<pt> apt=<primary>`.
209    /// Default `None` preserves single-codec SDP; answers still accept remote RTX.
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub rtx_payload_type: Option<u8>,
212}
213
214impl Default for VideoCapability {
215    fn default() -> Self {
216        Self {
217            payload_type: 96,
218            codec_name: "VP8".to_string(),
219            clock_rate: 90000,
220            fmtp: None,
221            rtcp_fbs: vec![
222                "nack".to_string(),
223                "nack pli".to_string(),
224                "ccm fir".to_string(),
225                "goog-remb".to_string(),
226                "transport-cc".to_string(),
227            ],
228            rtx_payload_type: None,
229        }
230    }
231}
232
233impl VideoCapability {
234    pub fn h264() -> Self {
235        Self {
236            payload_type: 96,
237            codec_name: "H264".to_string(),
238            clock_rate: 90000,
239            fmtp: Some("packetization-mode=1;profile-level-id=42e01f".to_string()),
240            rtcp_fbs: vec![
241                "nack".to_string(),
242                "nack pli".to_string(),
243                "ccm fir".to_string(),
244            ],
245            rtx_payload_type: None,
246        }
247    }
248
249    /// VP8 with RTX enabled (common browser interop profile).
250    pub fn vp8_with_rtx(rtx_payload_type: u8) -> Self {
251        Self {
252            rtx_payload_type: Some(rtx_payload_type),
253            ..Self::default()
254        }
255    }
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
259pub struct ApplicationCapability {
260    pub sctp_port: u16,
261}
262
263impl Default for ApplicationCapability {
264    fn default() -> Self {
265        Self { sctp_port: 5000 }
266    }
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
270pub enum T38FaxRateManagement {
271    #[serde(rename = "transferredTCF")]
272    #[default]
273    TransferredTCF,
274    #[serde(rename = "localTCF")]
275    LocalTCF,
276}
277
278impl std::fmt::Display for T38FaxRateManagement {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        match self {
281            Self::TransferredTCF => write!(f, "transferredTCF"),
282            Self::LocalTCF => write!(f, "localTCF"),
283        }
284    }
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
288pub enum T38UdpEC {
289    #[serde(rename = "t38UDPRedundancy")]
290    #[default]
291    T38UDPRedundancy,
292    #[serde(rename = "t38UDPFEC")]
293    T38UDPFEC,
294}
295
296impl std::fmt::Display for T38UdpEC {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        match self {
299            Self::T38UDPRedundancy => write!(f, "t38UDPRedundancy"),
300            Self::T38UDPFEC => write!(f, "t38UDPFEC"),
301        }
302    }
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
306pub struct T38Capability {
307    pub payload_type: u8,
308    /// T.38 version (0-3)
309    pub version: u8,
310    /// Max bit rate in bps (e.g. 14400, 9600, 4800, 2400)
311    pub max_bitrate: u32,
312    /// Rate management method
313    pub rate_management: T38FaxRateManagement,
314    /// Max buffer size in bytes
315    pub max_buffer: u16,
316    /// Max datagram size in bytes
317    pub max_datagram: u16,
318    /// UDP error correction method
319    pub udp_ec: T38UdpEC,
320    pub fmtp: Option<String>,
321}
322
323impl Default for T38Capability {
324    fn default() -> Self {
325        Self {
326            payload_type: 98,
327            version: 0,
328            max_bitrate: 14400,
329            rate_management: T38FaxRateManagement::default(),
330            max_buffer: 1024,
331            max_datagram: 238,
332            udp_ec: T38UdpEC::default(),
333            fmtp: None,
334        }
335    }
336}
337
338impl T38Capability {
339    pub fn default_t38() -> Self {
340        Self::default()
341    }
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
345pub struct MediaCapabilities {
346    pub audio: Vec<AudioCapability>,
347    pub video: Vec<VideoCapability>,
348    pub application: Option<ApplicationCapability>,
349    pub image: Vec<T38Capability>,
350}
351
352impl Default for MediaCapabilities {
353    fn default() -> Self {
354        Self {
355            audio: vec![AudioCapability::opus(), AudioCapability::pcmu()],
356            video: vec![VideoCapability::default()],
357            application: Some(ApplicationCapability::default()),
358            image: vec![],
359        }
360    }
361}
362
363#[derive(Clone)]
364pub struct DepacketizerStrategy {
365    pub factory: Arc<dyn DepacketizerFactory>,
366}
367
368impl Default for DepacketizerStrategy {
369    fn default() -> Self {
370        Self {
371            factory: Arc::new(DefaultDepacketizerFactory),
372        }
373    }
374}
375
376impl Debug for DepacketizerStrategy {
377    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
378        self.factory.fmt(f)
379    }
380}
381
382impl PartialEq for DepacketizerStrategy {
383    fn eq(&self, other: &Self) -> bool {
384        Arc::ptr_eq(&self.factory, &other.factory)
385    }
386}
387
388impl Eq for DepacketizerStrategy {}
389
390fn default_rtp_buffer_capacity() -> usize {
391    100
392}
393
394fn default_buffer_stats_log_interval() -> std::time::Duration {
395    std::time::Duration::from_secs(10)
396}
397
398/// Controls SDP generation compatibility for interoperability with legacy SIP endpoints.
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
400#[serde(rename_all = "snake_case")]
401pub enum SdpCompatibilityMode {
402    /// Standard WebRTC / RFC-compliant SDP output (default).
403    #[default]
404    Standard,
405    /// Compatibility mode for legacy SIP endpoints (e.g. Linphone):
406    /// omits `a=mid` unless BUNDLE is active, omits `a=rtcp-mux`.
407    LegacySip,
408}
409
410fn default_enable_upnp() -> bool {
411    false
412}
413
414fn default_upnp_lease_duration() -> u32 {
415    3600
416}
417
418fn default_upnp_discovery_timeout() -> std::time::Duration {
419    std::time::Duration::from_secs(1)
420}
421
422fn default_upnp_refresh_interval() -> std::time::Duration {
423    std::time::Duration::from_secs(30)
424}
425
426/// Primary configuration for a `PeerConnection`.
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct RtcConfiguration {
429    pub ice_servers: Vec<IceServer>,
430    pub ice_transport_policy: IceTransportPolicy,
431    pub bundle_policy: BundlePolicy,
432    pub rtcp_mux_policy: RtcpMuxPolicy,
433    pub certificates: Vec<CertificateConfig>,
434    pub transport_mode: TransportMode,
435    pub nack_buffer_size: usize,
436    pub media_capabilities: Option<MediaCapabilities>,
437    /// Override the advertised IP address in SDP (for NAT traversal).
438    /// When set, the `c=`, `o=`, and candidate addresses in the SDP will
439    /// use this IP instead of the local bind IP. The local bind address is
440    /// stored in `related_address` on the candidate.
441    pub external_ip: Option<String>,
442    /// Override the advertised port in SDP `m=` line and candidates
443    /// (for NAT port forwarding).
444    ///
445    /// When set, the SDP will advertise this port instead of the local
446    /// bind port. This is useful when you have configured NAT port
447    /// forwarding (e.g. external port 30000 → local port 20000) and need
448    /// the remote peer to send RTP to the external port.
449    ///
450    /// Works independently or combined with `external_ip`.
451    /// Only applies in RTP/SRTP direct mode (`TransportMode::Rtp` /
452    /// `TransportMode::Srtp`). Not used in WebRTC mode.
453    pub external_port: Option<u16>,
454    pub bind_ip: Option<String>,
455    pub disable_ipv6: bool,
456    pub ssrc_start: u32,
457    pub stun_timeout: std::time::Duration,
458    /// Timeout for the ICE nomination binding check (USE-CANDIDATE).
459    /// This should be larger than `stun_timeout` to allow more retransmissions
460    /// and reduce the probability of nomination failures under packet loss.
461    pub nomination_timeout: std::time::Duration,
462    pub ice_connection_timeout: std::time::Duration,
463    /// How long without receiving any packet (STUN/DTLS/SCTP) before the ICE
464    /// transport is demoted from `Connected` to `Disconnected`.
465    ///
466    /// `Disconnected` is a recoverable state — the SCTP association is **not**
467    /// torn down (see `peer_connection.rs`); the transport waits for traffic to
468    /// resume. Keep this comfortably below `ice_connection_timeout`, which is
469    /// the hard, non-recoverable failure threshold.
470    ///
471    /// Default: 30s. Raise it further (e.g. 120s) for long-lived
472    /// tunnels (SSH/port-forwarding) over lossy links where brief blackouts are
473    /// expected and must not even flap the SCTP association.
474    pub ice_disconnect_threshold: std::time::Duration,
475    /// How long to wait in `Disconnected` state before tearing down the
476    /// PeerConnection (SCTP/DTLS). When ICE goes `Disconnected` the transport
477    /// is given this long to recover before the connection is closed.
478    ///
479    /// This is distinct from `ice_connection_timeout` which operates at the
480    /// ICE transport layer. This grace period gives the application a chance
481    /// to observe `PeerConnectionState::Disconnected` and react, while still
482    /// bounding how long a dead connection lingers.
483    ///
484    /// Set to 0 to tear down immediately on ICE Disconnected.
485    /// Default: 60s
486    pub ice_disconnect_grace: std::time::Duration,
487    pub sctp_rto_initial: std::time::Duration,
488    pub sctp_rto_min: std::time::Duration,
489    pub sctp_rto_max: std::time::Duration,
490    pub sctp_max_association_retransmits: u32,
491    pub sctp_receive_window: usize,
492    pub sctp_heartbeat_interval: std::time::Duration,
493    pub sctp_max_heartbeat_failures: u32,
494    pub sctp_max_tsn_retransmits: u32,
495    pub sctp_max_burst: usize,
496    pub sctp_max_cwnd: usize,
497    /// Per-data-channel send-side buffered-amount limit in bytes (sum of
498    /// in-flight + queued payload bytes). The sender blocks when the total
499    /// exceeds this threshold, bounding per-channel peak RSS regardless of
500    /// peer speed. Default 256 KB;  0 disables the gate (unbounded).
501    pub sctp_max_buffered_amount: usize,
502    pub dtls_buffer_size: usize,
503    pub rtp_start_port: Option<u16>,
504    pub rtp_end_port: Option<u16>,
505    pub ice_gather_udp_hosts: bool,
506    /// Whether to gather and advertise loopback (127.0.0.1 / ::1) host
507    /// candidates when no explicit `bind_ip` is configured.
508    ///
509    /// Default: false — loopback candidates are unreachable by any remote
510    /// peer: they leak host internals in SDP and make remote agents waste
511    /// TURN permissions and connectivity checks on dead candidates (a same-
512    /// host TURN relay can even forward them back to itself). Set to `true`
513    /// for same-host testing where no non-loopback interface exists. An
514    /// explicit `bind_ip = "127.0.0.1"` is unaffected by this flag.
515    #[serde(default)]
516    pub ice_include_loopback_candidates: bool,
517    pub tcp_port_range_start: Option<u16>,
518    pub tcp_port_range_end: Option<u16>,
519    pub enable_latching: bool,
520    pub probation_max_packets: Option<u8>,
521    pub enable_ice_lite: bool,
522    /// When true, demote host candidates with private (RFC 1918) local IPs
523    /// below server-reflexive candidates in the connectivity check ordering.
524    /// This avoids DTLS handshake failures behind NATs where a host candidate
525    /// can pass a single STUN binding check but cannot sustain bidirectional
526    /// DTLS traffic.  Same-LAN pairs (both sides private) are not affected.
527    /// Default: false (standard RFC 5245 behavior).
528    #[serde(default)]
529    pub prefer_srflx_over_natted_host: bool,
530    /// Enable UPnP IGD for automatic port mapping
531    #[serde(default = "default_enable_upnp")]
532    pub enable_upnp: bool,
533    /// UPnP port mapping lease duration in seconds
534    #[serde(default = "default_upnp_lease_duration")]
535    pub upnp_lease_duration: u32,
536    /// UPnP gateway discovery timeout
537    #[serde(default = "default_upnp_discovery_timeout")]
538    pub upnp_discovery_timeout: std::time::Duration,
539    /// How often to refresh UPnP port mappings before the router lease expires.
540    /// Re-issuing AddPortMapping with the same external port renews the lease on
541    /// most IGDs without deleting the mapping, so long-lived sessions survive
542    /// the default lease (3600s) without inbound path loss.
543    #[serde(default = "default_upnp_refresh_interval")]
544    pub upnp_refresh_interval: std::time::Duration,
545    #[serde(skip, default)]
546    pub depacketizer_strategy: DepacketizerStrategy,
547    #[serde(default = "default_rtp_buffer_capacity")]
548    pub rtp_buffer_capacity: usize,
549    #[serde(default)]
550    pub buffer_drop_strategy: BufferDropStrategy,
551    #[serde(default = "default_buffer_stats_log_interval")]
552    pub buffer_stats_log_interval: std::time::Duration,
553    /// Controls ICE TCP candidate support (RFC 6544).
554    /// Default: Disabled — only UDP candidates are gathered and used.
555    #[serde(default)]
556    pub ice_tcp_policy: IceTcpPolicy,
557    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
558    ///
559    /// When `true`, multiple `PeerConnection`s share one `UdpSocket` bound to
560    /// `ice_udp_mux_port`. Incoming UDP packets are demultiplexed by the server
561    /// ufrag embedded in the first STUN Binding Request's `USERNAME` attribute,
562    /// and — once a pair is established — by the remote source address.
563    ///
564    /// Requires `ice_udp_mux_port` to be set. Useful for SFU/WHEP deployments
565    /// that need to advertise a single public UDP port for many sessions.
566    #[serde(default)]
567    pub ice_udp_mux: bool,
568    /// UDP port to bind the shared mux socket on. Required when `ice_udp_mux`
569    /// is enabled. All `PeerConnection`s sharing this port must agree on it.
570    #[serde(default)]
571    pub ice_udp_mux_port: Option<u16>,
572    /// SDP generation compatibility mode.
573    #[serde(default)]
574    pub sdp_compatibility: SdpCompatibilityMode,
575    #[serde(skip, default)]
576    pub label: Option<String>,
577    #[serde(skip, default)]
578    pub cname: Option<String>,
579    /// Runtime handle for spawning internal tasks (ICE runner, DTLS, RTCP,
580    /// etc.). When set, ALL rustrtc `tokio::spawn` calls use this handle,
581    /// pinning media tasks to a dedicated runtime instead of the caller's.
582    /// Falls back to `Handle::current()` when `None` (backward-compatible).
583    #[serde(skip, default)]
584    pub runtime_handle: Option<tokio::runtime::Handle>,
585    /// Recording / tapping interceptors installed on every transceiver
586    /// created by this PC. Receiver interceptors fire on incoming RTP
587    /// (pre-depacketize); sender interceptors fire on outgoing RTP
588    /// (post seq/timestamp rewrite, pre-wire).
589    #[serde(skip, default)]
590    pub recorder_interceptors: RecorderInterceptors,
591}
592
593impl PartialEq for RtcConfiguration {
594    fn eq(&self, other: &Self) -> bool {
595        // Compare all fields except runtime_handle (Handle does not implement Eq).
596        self.ice_servers == other.ice_servers
597            && self.ice_transport_policy == other.ice_transport_policy
598            && self.bundle_policy == other.bundle_policy
599            && self.rtcp_mux_policy == other.rtcp_mux_policy
600            && self.certificates == other.certificates
601            && self.transport_mode == other.transport_mode
602            && self.nack_buffer_size == other.nack_buffer_size
603            && self.media_capabilities == other.media_capabilities
604            && self.external_ip == other.external_ip
605            && self.external_port == other.external_port
606            && self.bind_ip == other.bind_ip
607            && self.disable_ipv6 == other.disable_ipv6
608            && self.ssrc_start == other.ssrc_start
609            && self.stun_timeout == other.stun_timeout
610            && self.nomination_timeout == other.nomination_timeout
611            && self.ice_connection_timeout == other.ice_connection_timeout
612            && self.ice_disconnect_threshold == other.ice_disconnect_threshold
613            && self.ice_disconnect_grace == other.ice_disconnect_grace
614            && self.sctp_rto_initial == other.sctp_rto_initial
615            && self.sctp_rto_min == other.sctp_rto_min
616            && self.sctp_rto_max == other.sctp_rto_max
617            && self.sctp_max_association_retransmits == other.sctp_max_association_retransmits
618            && self.sctp_receive_window == other.sctp_receive_window
619            && self.sctp_heartbeat_interval == other.sctp_heartbeat_interval
620            && self.sctp_max_heartbeat_failures == other.sctp_max_heartbeat_failures
621            && self.sctp_max_tsn_retransmits == other.sctp_max_tsn_retransmits
622            && self.sctp_max_burst == other.sctp_max_burst
623            && self.sctp_max_cwnd == other.sctp_max_cwnd
624            && self.sctp_max_buffered_amount == other.sctp_max_buffered_amount
625            && self.dtls_buffer_size == other.dtls_buffer_size
626            && self.rtp_start_port == other.rtp_start_port
627            && self.rtp_end_port == other.rtp_end_port
628            && self.ice_gather_udp_hosts == other.ice_gather_udp_hosts
629            && self.ice_include_loopback_candidates == other.ice_include_loopback_candidates
630            && self.tcp_port_range_start == other.tcp_port_range_start
631            && self.tcp_port_range_end == other.tcp_port_range_end
632            && self.enable_latching == other.enable_latching
633            && self.probation_max_packets == other.probation_max_packets
634            && self.enable_ice_lite == other.enable_ice_lite
635            && self.prefer_srflx_over_natted_host == other.prefer_srflx_over_natted_host
636            && self.enable_upnp == other.enable_upnp
637            && self.upnp_lease_duration == other.upnp_lease_duration
638            && self.upnp_discovery_timeout == other.upnp_discovery_timeout
639            && self.upnp_refresh_interval == other.upnp_refresh_interval
640            && self.depacketizer_strategy == other.depacketizer_strategy
641            && self.rtp_buffer_capacity == other.rtp_buffer_capacity
642            && self.buffer_drop_strategy == other.buffer_drop_strategy
643            && self.buffer_stats_log_interval == other.buffer_stats_log_interval
644            && self.ice_tcp_policy == other.ice_tcp_policy
645            && self.ice_udp_mux == other.ice_udp_mux
646            && self.ice_udp_mux_port == other.ice_udp_mux_port
647            && self.sdp_compatibility == other.sdp_compatibility
648            && self.label == other.label
649            && self.cname == other.cname
650        // runtime_handle is intentionally omitted
651    }
652}
653
654impl Eq for RtcConfiguration {}
655
656impl Default for RtcConfiguration {
657    fn default() -> Self {
658        Self {
659            ice_servers: Vec::new(),
660            ice_transport_policy: IceTransportPolicy::default(),
661            bundle_policy: BundlePolicy::default(),
662            rtcp_mux_policy: RtcpMuxPolicy::default(),
663            certificates: Vec::new(),
664            transport_mode: TransportMode::default(),
665            nack_buffer_size: 200,
666            media_capabilities: None,
667            external_ip: None,
668            external_port: None,
669            bind_ip: None,
670            disable_ipv6: false,
671            ssrc_start: 10000,
672            stun_timeout: std::time::Duration::from_secs(5),
673            nomination_timeout: std::time::Duration::from_secs(10),
674            ice_connection_timeout: std::time::Duration::from_secs(120),
675            ice_disconnect_threshold: std::time::Duration::from_secs(30),
676            ice_disconnect_grace: std::time::Duration::from_secs(60),
677            sctp_rto_initial: std::time::Duration::from_secs(3),
678            sctp_rto_min: std::time::Duration::from_millis(200),
679            sctp_rto_max: std::time::Duration::from_secs(60),
680            sctp_max_association_retransmits: 20,
681            sctp_receive_window: 128 * 1024, // 128KB - reduced for lower memory footprint
682            sctp_heartbeat_interval: std::time::Duration::from_secs(15),
683            sctp_max_heartbeat_failures: 4,
684            sctp_max_tsn_retransmits: 8,
685            sctp_max_burst: 0,                    // 0 = use default heuristic
686            sctp_max_cwnd: 256 * 1024,            // 256 KB
687            sctp_max_buffered_amount: 256 * 1024, // 256 KB
688            dtls_buffer_size: 2048,
689            rtp_start_port: None,
690            rtp_end_port: None,
691            ice_gather_udp_hosts: true,
692            ice_include_loopback_candidates: false,
693            tcp_port_range_start: None,
694            tcp_port_range_end: None,
695            enable_latching: false,
696            probation_max_packets: None,
697            enable_ice_lite: false,
698            prefer_srflx_over_natted_host: false,
699            enable_upnp: default_enable_upnp(),
700            upnp_lease_duration: default_upnp_lease_duration(),
701            upnp_discovery_timeout: default_upnp_discovery_timeout(),
702            upnp_refresh_interval: default_upnp_refresh_interval(),
703            depacketizer_strategy: DepacketizerStrategy::default(),
704            rtp_buffer_capacity: default_rtp_buffer_capacity(),
705            buffer_drop_strategy: BufferDropStrategy::default(),
706            buffer_stats_log_interval: default_buffer_stats_log_interval(),
707            ice_tcp_policy: IceTcpPolicy::default(),
708            ice_udp_mux: false,
709            ice_udp_mux_port: None,
710            sdp_compatibility: SdpCompatibilityMode::default(),
711            label: None,
712            cname: None,
713            runtime_handle: None,
714            recorder_interceptors: RecorderInterceptors::default(),
715        }
716    }
717}
718
719pub struct RtcConfigurationBuilder {
720    inner: RtcConfiguration,
721}
722
723impl Default for RtcConfigurationBuilder {
724    fn default() -> Self {
725        Self::new()
726    }
727}
728
729/// Wrapper for interceptor lists so that `RtcConfiguration` can still derive
730/// `Debug / Clone / PartialEq / Eq` — the actual interceptor trait objects
731/// are opaque and only compared by count.
732#[derive(Clone, Default)]
733pub struct RecorderInterceptors {
734    pub receivers: Vec<Arc<dyn RtpReceiverInterceptor>>,
735    pub senders: Vec<Arc<dyn RtpSenderInterceptor>>,
736}
737
738impl Debug for RecorderInterceptors {
739    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
740        f.debug_struct("RecorderInterceptors")
741            .field("receivers_len", &self.receivers.len())
742            .field("senders_len", &self.senders.len())
743            .finish()
744    }
745}
746
747impl PartialEq for RecorderInterceptors {
748    fn eq(&self, other: &Self) -> bool {
749        self.receivers.len() == other.receivers.len() && self.senders.len() == other.senders.len()
750    }
751}
752
753impl Eq for RecorderInterceptors {}
754
755impl RtcConfigurationBuilder {
756    pub fn new() -> Self {
757        Self {
758            inner: RtcConfiguration::default(),
759        }
760    }
761
762    pub fn enable_latching(mut self, enable: bool) -> Self {
763        self.inner.enable_latching = enable;
764        self
765    }
766
767    pub fn probation_max_packets(mut self, max: Option<u8>) -> Self {
768        self.inner.probation_max_packets = max;
769        self
770    }
771
772    pub fn enable_ice_lite(mut self, enable: bool) -> Self {
773        self.inner.enable_ice_lite = enable;
774        self
775    }
776
777    pub fn prefer_srflx_over_natted_host(mut self, enable: bool) -> Self {
778        self.inner.prefer_srflx_over_natted_host = enable;
779        self
780    }
781
782    pub fn enable_upnp(mut self, enable: bool) -> Self {
783        self.inner.enable_upnp = enable;
784        self
785    }
786
787    pub fn upnp_lease_duration(mut self, duration_secs: u32) -> Self {
788        self.inner.upnp_lease_duration = duration_secs;
789        self
790    }
791
792    pub fn upnp_discovery_timeout(mut self, timeout: std::time::Duration) -> Self {
793        self.inner.upnp_discovery_timeout = timeout;
794        self
795    }
796
797    /// Set how often UPnP port mappings are refreshed to keep the router lease alive.
798    pub fn upnp_refresh_interval(mut self, interval: std::time::Duration) -> Self {
799        self.inner.upnp_refresh_interval = interval;
800        self
801    }
802
803    pub fn ice_server(mut self, server: IceServer) -> Self {
804        self.inner.ice_servers.push(server);
805        self
806    }
807
808    pub fn ice_transport_policy(mut self, policy: IceTransportPolicy) -> Self {
809        self.inner.ice_transport_policy = policy;
810        self
811    }
812
813    pub fn bundle_policy(mut self, policy: BundlePolicy) -> Self {
814        self.inner.bundle_policy = policy;
815        self
816    }
817
818    pub fn rtcp_mux_policy(mut self, policy: RtcpMuxPolicy) -> Self {
819        self.inner.rtcp_mux_policy = policy;
820        self
821    }
822
823    pub fn certificate(mut self, cert: CertificateConfig) -> Self {
824        self.inner.certificates.push(cert);
825        self
826    }
827
828    pub fn transport_mode(mut self, mode: TransportMode) -> Self {
829        self.inner.transport_mode = mode;
830        self
831    }
832
833    pub fn media_capabilities(mut self, capabilities: MediaCapabilities) -> Self {
834        self.inner.media_capabilities = Some(capabilities);
835        self
836    }
837
838    pub fn external_ip(mut self, ip: String) -> Self {
839        self.inner.external_ip = Some(ip);
840        self
841    }
842
843    pub fn external_port(mut self, port: u16) -> Self {
844        self.inner.external_port = Some(port);
845        self
846    }
847
848    pub fn bind_ip(mut self, ip: String) -> Self {
849        self.inner.bind_ip = Some(ip);
850        self
851    }
852
853    pub fn disable_ipv6(mut self, disable: bool) -> Self {
854        self.inner.disable_ipv6 = disable;
855        self
856    }
857
858    pub fn ssrc_start(mut self, start: u32) -> Self {
859        self.inner.ssrc_start = start;
860        self
861    }
862
863    pub fn stun_timeout(mut self, timeout: std::time::Duration) -> Self {
864        self.inner.stun_timeout = timeout;
865        self
866    }
867
868    pub fn nomination_timeout(mut self, timeout: std::time::Duration) -> Self {
869        self.inner.nomination_timeout = timeout;
870        self
871    }
872
873    pub fn rtp_port_range(mut self, start: u16, end: u16) -> Self {
874        self.inner.rtp_start_port = Some(start);
875        self.inner.rtp_end_port = Some(end);
876        self
877    }
878
879    pub fn ice_gather_udp_hosts(mut self, enable: bool) -> Self {
880        self.inner.ice_gather_udp_hosts = enable;
881        self
882    }
883
884    /// Gather and advertise loopback host candidates (default false). See
885    /// `RtcConfiguration::ice_include_loopback_candidates`.
886    pub fn ice_include_loopback_candidates(mut self, enable: bool) -> Self {
887        self.inner.ice_include_loopback_candidates = enable;
888        self
889    }
890
891    pub fn tcp_port_range(mut self, start: u16, end: u16) -> Self {
892        self.inner.tcp_port_range_start = Some(start);
893        self.inner.tcp_port_range_end = Some(end);
894        self
895    }
896
897    pub fn dtls_buffer_size(mut self, size: usize) -> Self {
898        self.inner.dtls_buffer_size = size;
899        self
900    }
901
902    pub fn sctp_rto_initial(mut self, duration: std::time::Duration) -> Self {
903        self.inner.sctp_rto_initial = duration;
904        self
905    }
906
907    pub fn sctp_rto_min(mut self, duration: std::time::Duration) -> Self {
908        self.inner.sctp_rto_min = duration;
909        self
910    }
911
912    pub fn sctp_rto_max(mut self, duration: std::time::Duration) -> Self {
913        self.inner.sctp_rto_max = duration;
914        self
915    }
916
917    pub fn sctp_max_association_retransmits(mut self, count: u32) -> Self {
918        self.inner.sctp_max_association_retransmits = count;
919        self
920    }
921
922    pub fn sctp_receive_window(mut self, size: usize) -> Self {
923        self.inner.sctp_receive_window = size;
924        self
925    }
926
927    pub fn sctp_heartbeat_interval(mut self, duration: std::time::Duration) -> Self {
928        self.inner.sctp_heartbeat_interval = duration;
929        self
930    }
931
932    pub fn sctp_max_heartbeat_failures(mut self, count: u32) -> Self {
933        self.inner.sctp_max_heartbeat_failures = count;
934        self
935    }
936
937    /// Set the maximum burst size for SCTP in number of MTU-sized packets.
938    /// 0 means use the default heuristic (16 packets normal, 4 in recovery).
939    /// For rate-limited TURN relays, a value of 2-4 can reduce burst-induced
940    /// packet loss.
941    pub fn sctp_max_burst(mut self, packets: usize) -> Self {
942        self.inner.sctp_max_burst = packets;
943        self
944    }
945
946    /// Set the maximum congestion window size in bytes.
947    /// Default is 256 KB. For high-latency TURN relays, consider 512KB-1MB.
948    pub fn sctp_max_cwnd(mut self, size: usize) -> Self {
949        self.inner.sctp_max_cwnd = size;
950        self
951    }
952
953    /// Per-data-channel send-side buffered-amount limit in bytes.
954    /// Default is 256 KB. Set to 0 for unbounded (compatibility with pre-0.3.114
955    /// behavior that had no gate). A non-zero limit bounds the sum of in-flight
956    /// and queued payload bytes per data channel, blocking `send()`/`send_text()`
957    /// until the buffer drains below the threshold.
958    pub fn sctp_max_buffered_amount(mut self, bytes: usize) -> Self {
959        self.inner.sctp_max_buffered_amount = bytes;
960        self
961    }
962
963    pub fn ice_connection_timeout(mut self, timeout: std::time::Duration) -> Self {
964        self.inner.ice_connection_timeout = timeout;
965        self
966    }
967
968    pub fn ice_disconnect_threshold(mut self, threshold: std::time::Duration) -> Self {
969        self.inner.ice_disconnect_threshold = threshold;
970        self
971    }
972
973    pub fn ice_disconnect_grace(mut self, grace: std::time::Duration) -> Self {
974        self.inner.ice_disconnect_grace = grace;
975        self
976    }
977
978    pub fn rtp_buffer_capacity(mut self, capacity: usize) -> Self {
979        self.inner.rtp_buffer_capacity = capacity;
980        self
981    }
982
983    pub fn buffer_drop_strategy(mut self, strategy: BufferDropStrategy) -> Self {
984        self.inner.buffer_drop_strategy = strategy;
985        self
986    }
987
988    pub fn buffer_stats_log_interval(mut self, interval: std::time::Duration) -> Self {
989        self.inner.buffer_stats_log_interval = interval;
990        self
991    }
992
993    pub fn ice_tcp_policy(mut self, policy: IceTcpPolicy) -> Self {
994        self.inner.ice_tcp_policy = policy;
995        self
996    }
997
998    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
999    /// Requires `ice_udp_mux_port` to also be set.
1000    pub fn ice_udp_mux(mut self, enable: bool) -> Self {
1001        self.inner.ice_udp_mux = enable;
1002        self
1003    }
1004
1005    /// Set the shared UDP mux port. Must be set when `ice_udp_mux` is enabled.
1006    pub fn ice_udp_mux_port(mut self, port: u16) -> Self {
1007        self.inner.ice_udp_mux_port = Some(port);
1008        self
1009    }
1010
1011    pub fn sdp_compatibility(mut self, mode: SdpCompatibilityMode) -> Self {
1012        self.inner.sdp_compatibility = mode;
1013        self
1014    }
1015
1016    pub fn cname(mut self, cname: String) -> Self {
1017        self.inner.cname = Some(cname);
1018        self
1019    }
1020
1021    /// Append a receiver interceptor (fires on every incoming RTP packet,
1022    /// pre-depacketize).
1023    pub fn receiver_interceptor(mut self, interceptor: Arc<dyn RtpReceiverInterceptor>) -> Self {
1024        self.inner.recorder_interceptors.receivers.push(interceptor);
1025        self
1026    }
1027
1028    /// Append a sender interceptor (fires on every outgoing RTP packet,
1029    /// post seq/timestamp rewrite).
1030    pub fn sender_interceptor(mut self, interceptor: Arc<dyn RtpSenderInterceptor>) -> Self {
1031        self.inner.recorder_interceptors.senders.push(interceptor);
1032        self
1033    }
1034
1035    /// Set the runtime handle for spawning internal tasks (ICE runner, DTLS,
1036    /// RTCP loops, etc.). When set, all rustrtc-internal tokio::spawn calls
1037    /// go through this runtime instead of the ambient tokio runtime.
1038    pub fn runtime_handle(mut self, handle: tokio::runtime::Handle) -> Self {
1039        self.inner.runtime_handle = Some(handle);
1040        self
1041    }
1042
1043    pub fn build(self) -> RtcConfiguration {
1044        self.inner
1045    }
1046}
1047
1048impl From<RtcConfigurationBuilder> for RtcConfiguration {
1049    fn from(builder: RtcConfigurationBuilder) -> Self {
1050        builder.build()
1051    }
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056    use super::*;
1057    use std::time::Duration;
1058
1059    #[test]
1060    fn test_rtc_configuration_defaults() {
1061        let config = RtcConfiguration::default();
1062        assert_eq!(config.ice_connection_timeout, Duration::from_secs(120));
1063        assert_eq!(config.ice_disconnect_threshold, Duration::from_secs(30));
1064        assert_eq!(config.ice_disconnect_grace, Duration::from_secs(60));
1065        assert_eq!(config.sctp_rto_initial, Duration::from_secs(3));
1066        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
1067        assert_eq!(config.sctp_rto_max, Duration::from_secs(60));
1068        assert_eq!(config.sctp_max_association_retransmits, 20);
1069        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(15));
1070        assert_eq!(config.sctp_max_heartbeat_failures, 4);
1071        assert_eq!(config.sctp_max_burst, 0);
1072        assert_eq!(config.sctp_max_cwnd, 256 * 1024);
1073        assert_eq!(config.rtp_buffer_capacity, 100);
1074        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropNew);
1075        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(10));
1076    }
1077
1078    #[test]
1079    fn test_rtc_configuration_builder() {
1080        let config = RtcConfigurationBuilder::new()
1081            .stun_timeout(Duration::from_secs(10))
1082            .build();
1083        assert_eq!(config.stun_timeout, Duration::from_secs(10));
1084        // Verify other defaults are still there
1085        assert_eq!(config.ice_connection_timeout, Duration::from_secs(120));
1086    }
1087
1088    #[test]
1089    fn test_buffer_config_builder() {
1090        let config = RtcConfigurationBuilder::new()
1091            .rtp_buffer_capacity(200)
1092            .buffer_drop_strategy(BufferDropStrategy::DropOldest)
1093            .buffer_stats_log_interval(Duration::from_secs(5))
1094            .build();
1095        assert_eq!(config.rtp_buffer_capacity, 200);
1096        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropOldest);
1097        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(5));
1098    }
1099
1100    #[test]
1101    fn test_sctp_builder_methods() {
1102        let config = RtcConfigurationBuilder::new()
1103            .sctp_rto_initial(Duration::from_millis(500))
1104            .sctp_rto_min(Duration::from_millis(200))
1105            .sctp_rto_max(Duration::from_secs(10))
1106            .sctp_max_association_retransmits(30)
1107            .sctp_receive_window(512 * 1024)
1108            .sctp_heartbeat_interval(Duration::from_secs(10))
1109            .sctp_max_heartbeat_failures(8)
1110            .sctp_max_burst(4)
1111            .sctp_max_cwnd(512 * 1024)
1112            .ice_connection_timeout(Duration::from_secs(60))
1113            .build();
1114
1115        assert_eq!(config.sctp_rto_initial, Duration::from_millis(500));
1116        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
1117        assert_eq!(config.sctp_rto_max, Duration::from_secs(10));
1118        assert_eq!(config.sctp_max_association_retransmits, 30);
1119        assert_eq!(config.sctp_receive_window, 512 * 1024);
1120        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(10));
1121        assert_eq!(config.sctp_max_heartbeat_failures, 8);
1122        assert_eq!(config.sctp_max_burst, 4);
1123        assert_eq!(config.sctp_max_cwnd, 512 * 1024);
1124        assert_eq!(config.ice_connection_timeout, Duration::from_secs(60));
1125    }
1126
1127    #[test]
1128    fn test_turn_optimized_config() {
1129        // Verify a TURN-optimized configuration can be expressed cleanly
1130        let config = RtcConfigurationBuilder::new()
1131            .sctp_rto_initial(Duration::from_millis(500))
1132            .sctp_rto_min(Duration::from_millis(100))
1133            .sctp_rto_max(Duration::from_secs(10))
1134            .sctp_max_association_retransmits(30)
1135            .sctp_max_heartbeat_failures(8)
1136            .sctp_max_burst(4)
1137            .stun_timeout(Duration::from_secs(10))
1138            .nomination_timeout(Duration::from_secs(20))
1139            .build();
1140
1141        // Verify the TURN-optimized values are more aggressive than defaults
1142        let defaults = RtcConfiguration::default();
1143        assert!(config.sctp_rto_initial < defaults.sctp_rto_initial);
1144        assert!(config.sctp_rto_min < defaults.sctp_rto_min);
1145        assert!(config.sctp_rto_max < defaults.sctp_rto_max);
1146        assert!(
1147            config.sctp_max_association_retransmits > defaults.sctp_max_association_retransmits
1148        );
1149        assert!(config.sctp_max_heartbeat_failures > defaults.sctp_max_heartbeat_failures);
1150        assert!(config.sctp_max_burst > 0); // Explicit burst limit vs. heuristic
1151    }
1152
1153    #[test]
1154    fn test_external_port_defaults() {
1155        let config = RtcConfiguration::default();
1156        assert_eq!(config.external_port, None);
1157    }
1158
1159    #[test]
1160    fn test_external_port_builder() {
1161        let config = RtcConfigurationBuilder::new().external_port(30000).build();
1162        assert_eq!(config.external_port, Some(30000));
1163    }
1164
1165    #[test]
1166    fn test_external_port_with_external_ip_builder() {
1167        let config = RtcConfigurationBuilder::new()
1168            .external_ip("203.0.113.5".to_string())
1169            .external_port(30000)
1170            .build();
1171        assert_eq!(config.external_ip, Some("203.0.113.5".to_string()));
1172        assert_eq!(config.external_port, Some(30000));
1173    }
1174
1175    #[test]
1176    fn test_upnp_defaults() {
1177        let config = RtcConfiguration::default();
1178        assert!(!config.enable_upnp, "UPnP should be disabled by default");
1179        assert_eq!(config.upnp_lease_duration, 3600);
1180        assert_eq!(config.upnp_refresh_interval, Duration::from_secs(30));
1181    }
1182
1183    #[test]
1184    fn test_upnp_builder_methods() {
1185        let config = RtcConfigurationBuilder::new()
1186            .enable_upnp(false)
1187            .upnp_lease_duration(7200)
1188            .build();
1189        assert!(!config.enable_upnp);
1190        assert_eq!(config.upnp_lease_duration, 7200);
1191    }
1192
1193    #[test]
1194    fn test_upnp_refresh_interval_builder() {
1195        let config = RtcConfigurationBuilder::new()
1196            .upnp_refresh_interval(Duration::from_secs(60))
1197            .build();
1198        assert_eq!(config.upnp_refresh_interval, Duration::from_secs(60));
1199
1200        // Refresh interval participates in PartialEq
1201        let a = RtcConfigurationBuilder::new()
1202            .upnp_refresh_interval(Duration::from_secs(60))
1203            .build();
1204        let b = RtcConfigurationBuilder::new()
1205            .upnp_refresh_interval(Duration::from_secs(120))
1206            .build();
1207        assert_ne!(a, b);
1208        assert_eq!(a, a.clone());
1209    }
1210
1211    #[test]
1212    fn test_upnp_optimized_config() {
1213        let config = RtcConfigurationBuilder::new()
1214            .enable_upnp(true)
1215            .upnp_lease_duration(1800)
1216            .build();
1217
1218        assert!(config.enable_upnp);
1219        assert_eq!(config.upnp_lease_duration, 1800);
1220
1221        // Verify defaults remain for other options
1222        let defaults = RtcConfiguration::default();
1223        assert_eq!(
1224            config.ice_connection_timeout,
1225            defaults.ice_connection_timeout
1226        );
1227    }
1228
1229    #[test]
1230    fn test_ice_udp_mux_defaults() {
1231        let config = RtcConfiguration::default();
1232        assert!(
1233            !config.ice_udp_mux,
1234            "ICE UDP mux should be disabled by default"
1235        );
1236        assert_eq!(config.ice_udp_mux_port, None);
1237    }
1238
1239    #[test]
1240    fn test_ice_udp_mux_builder_methods() {
1241        let config = RtcConfigurationBuilder::new()
1242            .ice_udp_mux(true)
1243            .ice_udp_mux_port(30500)
1244            .build();
1245        assert!(config.ice_udp_mux);
1246        assert_eq!(config.ice_udp_mux_port, Some(30500));
1247    }
1248}