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!["nack pli".to_string(), "ccm fir".to_string()],
241            rtx_payload_type: None,
242        }
243    }
244
245    /// VP8 with RTX enabled (common browser interop profile).
246    pub fn vp8_with_rtx(rtx_payload_type: u8) -> Self {
247        Self {
248            rtx_payload_type: Some(rtx_payload_type),
249            ..Self::default()
250        }
251    }
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
255pub struct ApplicationCapability {
256    pub sctp_port: u16,
257}
258
259impl Default for ApplicationCapability {
260    fn default() -> Self {
261        Self { sctp_port: 5000 }
262    }
263}
264
265#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
266pub enum T38FaxRateManagement {
267    #[serde(rename = "transferredTCF")]
268    #[default]
269    TransferredTCF,
270    #[serde(rename = "localTCF")]
271    LocalTCF,
272}
273
274impl std::fmt::Display for T38FaxRateManagement {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        match self {
277            Self::TransferredTCF => write!(f, "transferredTCF"),
278            Self::LocalTCF => write!(f, "localTCF"),
279        }
280    }
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
284pub enum T38UdpEC {
285    #[serde(rename = "t38UDPRedundancy")]
286    #[default]
287    T38UDPRedundancy,
288    #[serde(rename = "t38UDPFEC")]
289    T38UDPFEC,
290}
291
292impl std::fmt::Display for T38UdpEC {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        match self {
295            Self::T38UDPRedundancy => write!(f, "t38UDPRedundancy"),
296            Self::T38UDPFEC => write!(f, "t38UDPFEC"),
297        }
298    }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
302pub struct T38Capability {
303    pub payload_type: u8,
304    /// T.38 version (0-3)
305    pub version: u8,
306    /// Max bit rate in bps (e.g. 14400, 9600, 4800, 2400)
307    pub max_bitrate: u32,
308    /// Rate management method
309    pub rate_management: T38FaxRateManagement,
310    /// Max buffer size in bytes
311    pub max_buffer: u16,
312    /// Max datagram size in bytes
313    pub max_datagram: u16,
314    /// UDP error correction method
315    pub udp_ec: T38UdpEC,
316    pub fmtp: Option<String>,
317}
318
319impl Default for T38Capability {
320    fn default() -> Self {
321        Self {
322            payload_type: 98,
323            version: 0,
324            max_bitrate: 14400,
325            rate_management: T38FaxRateManagement::default(),
326            max_buffer: 1024,
327            max_datagram: 238,
328            udp_ec: T38UdpEC::default(),
329            fmtp: None,
330        }
331    }
332}
333
334impl T38Capability {
335    pub fn default_t38() -> Self {
336        Self::default()
337    }
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
341pub struct MediaCapabilities {
342    pub audio: Vec<AudioCapability>,
343    pub video: Vec<VideoCapability>,
344    pub application: Option<ApplicationCapability>,
345    pub image: Vec<T38Capability>,
346}
347
348impl Default for MediaCapabilities {
349    fn default() -> Self {
350        Self {
351            audio: vec![AudioCapability::opus(), AudioCapability::pcmu()],
352            video: vec![VideoCapability::default()],
353            application: Some(ApplicationCapability::default()),
354            image: vec![],
355        }
356    }
357}
358
359#[derive(Clone)]
360pub struct DepacketizerStrategy {
361    pub factory: Arc<dyn DepacketizerFactory>,
362}
363
364impl Default for DepacketizerStrategy {
365    fn default() -> Self {
366        Self {
367            factory: Arc::new(DefaultDepacketizerFactory),
368        }
369    }
370}
371
372impl Debug for DepacketizerStrategy {
373    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
374        self.factory.fmt(f)
375    }
376}
377
378impl PartialEq for DepacketizerStrategy {
379    fn eq(&self, other: &Self) -> bool {
380        Arc::ptr_eq(&self.factory, &other.factory)
381    }
382}
383
384impl Eq for DepacketizerStrategy {}
385
386fn default_rtp_buffer_capacity() -> usize {
387    100
388}
389
390fn default_buffer_stats_log_interval() -> std::time::Duration {
391    std::time::Duration::from_secs(10)
392}
393
394/// Controls SDP generation compatibility for interoperability with legacy SIP endpoints.
395#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
396#[serde(rename_all = "snake_case")]
397pub enum SdpCompatibilityMode {
398    /// Standard WebRTC / RFC-compliant SDP output (default).
399    #[default]
400    Standard,
401    /// Compatibility mode for legacy SIP endpoints (e.g. Linphone):
402    /// omits `a=mid` unless BUNDLE is active, omits `a=rtcp-mux`.
403    LegacySip,
404}
405
406fn default_enable_upnp() -> bool {
407    false
408}
409
410fn default_upnp_lease_duration() -> u32 {
411    3600
412}
413
414fn default_upnp_discovery_timeout() -> std::time::Duration {
415    std::time::Duration::from_secs(1)
416}
417
418/// Primary configuration for a `PeerConnection`.
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct RtcConfiguration {
421    pub ice_servers: Vec<IceServer>,
422    pub ice_transport_policy: IceTransportPolicy,
423    pub bundle_policy: BundlePolicy,
424    pub rtcp_mux_policy: RtcpMuxPolicy,
425    pub certificates: Vec<CertificateConfig>,
426    pub transport_mode: TransportMode,
427    pub nack_buffer_size: usize,
428    pub media_capabilities: Option<MediaCapabilities>,
429    /// Override the advertised IP address in SDP (for NAT traversal).
430    /// When set, the `c=`, `o=`, and candidate addresses in the SDP will
431    /// use this IP instead of the local bind IP. The local bind address is
432    /// stored in `related_address` on the candidate.
433    pub external_ip: Option<String>,
434    /// Override the advertised port in SDP `m=` line and candidates
435    /// (for NAT port forwarding).
436    ///
437    /// When set, the SDP will advertise this port instead of the local
438    /// bind port. This is useful when you have configured NAT port
439    /// forwarding (e.g. external port 30000 → local port 20000) and need
440    /// the remote peer to send RTP to the external port.
441    ///
442    /// Works independently or combined with `external_ip`.
443    /// Only applies in RTP/SRTP direct mode (`TransportMode::Rtp` /
444    /// `TransportMode::Srtp`). Not used in WebRTC mode.
445    pub external_port: Option<u16>,
446    pub bind_ip: Option<String>,
447    pub disable_ipv6: bool,
448    pub ssrc_start: u32,
449    pub stun_timeout: std::time::Duration,
450    /// Timeout for the ICE nomination binding check (USE-CANDIDATE).
451    /// This should be larger than `stun_timeout` to allow more retransmissions
452    /// and reduce the probability of nomination failures under packet loss.
453    pub nomination_timeout: std::time::Duration,
454    pub ice_connection_timeout: std::time::Duration,
455    /// How long without receiving any packet (STUN/DTLS/SCTP) before the ICE
456    /// transport is demoted from `Connected` to `Disconnected`.
457    ///
458    /// `Disconnected` is a recoverable state — the SCTP association is **not**
459    /// torn down (see `peer_connection.rs`); the transport waits for traffic to
460    /// resume. Keep this comfortably below `ice_connection_timeout`, which is
461    /// the hard, non-recoverable failure threshold.
462    ///
463    /// Default: 5s (standard heuristic). Raise it (e.g. 120s) for long-lived
464    /// tunnels (SSH/port-forwarding) over lossy links where brief blackouts are
465    /// expected and must not even flap the SCTP association.
466    pub ice_disconnect_threshold: std::time::Duration,
467    /// How long to wait in `Disconnected` state before tearing down the
468    /// PeerConnection (SCTP/DTLS). When ICE goes `Disconnected` the transport
469    /// is given this long to recover before the connection is closed.
470    ///
471    /// This is distinct from `ice_connection_timeout` which operates at the
472    /// ICE transport layer. This grace period gives the application a chance
473    /// to observe `PeerConnectionState::Disconnected` and react, while still
474    /// bounding how long a dead connection lingers.
475    ///
476    /// Set to 0 to tear down immediately on ICE Disconnected.
477    /// Default: 15s
478    pub ice_disconnect_grace: std::time::Duration,
479    pub sctp_rto_initial: std::time::Duration,
480    pub sctp_rto_min: std::time::Duration,
481    pub sctp_rto_max: std::time::Duration,
482    pub sctp_max_association_retransmits: u32,
483    pub sctp_receive_window: usize,
484    pub sctp_heartbeat_interval: std::time::Duration,
485    pub sctp_max_heartbeat_failures: u32,
486    pub sctp_max_tsn_retransmits: u32,
487    pub sctp_max_burst: usize,
488    pub sctp_max_cwnd: usize,
489    /// Per-data-channel send-side buffered-amount limit in bytes (sum of
490    /// in-flight + queued payload bytes). The sender blocks when the total
491    /// exceeds this threshold, bounding per-channel peak RSS regardless of
492    /// peer speed. Default 256 KB;  0 disables the gate (unbounded).
493    pub sctp_max_buffered_amount: usize,
494    pub dtls_buffer_size: usize,
495    pub rtp_start_port: Option<u16>,
496    pub rtp_end_port: Option<u16>,
497    pub ice_gather_udp_hosts: bool,
498    pub tcp_port_range_start: Option<u16>,
499    pub tcp_port_range_end: Option<u16>,
500    pub enable_latching: bool,
501    pub probation_max_packets: Option<u8>,
502    pub enable_ice_lite: bool,
503    /// When true, demote host candidates with private (RFC 1918) local IPs
504    /// below server-reflexive candidates in the connectivity check ordering.
505    /// This avoids DTLS handshake failures behind NATs where a host candidate
506    /// can pass a single STUN binding check but cannot sustain bidirectional
507    /// DTLS traffic.  Same-LAN pairs (both sides private) are not affected.
508    /// Default: false (standard RFC 5245 behavior).
509    #[serde(default)]
510    pub prefer_srflx_over_natted_host: bool,
511    /// Enable UPnP IGD for automatic port mapping
512    #[serde(default = "default_enable_upnp")]
513    pub enable_upnp: bool,
514    /// UPnP port mapping lease duration in seconds
515    #[serde(default = "default_upnp_lease_duration")]
516    pub upnp_lease_duration: u32,
517    /// UPnP gateway discovery timeout
518    #[serde(default = "default_upnp_discovery_timeout")]
519    pub upnp_discovery_timeout: std::time::Duration,
520    #[serde(skip, default)]
521    pub depacketizer_strategy: DepacketizerStrategy,
522    #[serde(default = "default_rtp_buffer_capacity")]
523    pub rtp_buffer_capacity: usize,
524    #[serde(default)]
525    pub buffer_drop_strategy: BufferDropStrategy,
526    #[serde(default = "default_buffer_stats_log_interval")]
527    pub buffer_stats_log_interval: std::time::Duration,
528    /// Controls ICE TCP candidate support (RFC 6544).
529    /// Default: Disabled — only UDP candidates are gathered and used.
530    #[serde(default)]
531    pub ice_tcp_policy: IceTcpPolicy,
532    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
533    ///
534    /// When `true`, multiple `PeerConnection`s share one `UdpSocket` bound to
535    /// `ice_udp_mux_port`. Incoming UDP packets are demultiplexed by the server
536    /// ufrag embedded in the first STUN Binding Request's `USERNAME` attribute,
537    /// and — once a pair is established — by the remote source address.
538    ///
539    /// Requires `ice_udp_mux_port` to be set. Useful for SFU/WHEP deployments
540    /// that need to advertise a single public UDP port for many sessions.
541    #[serde(default)]
542    pub ice_udp_mux: bool,
543    /// UDP port to bind the shared mux socket on. Required when `ice_udp_mux`
544    /// is enabled. All `PeerConnection`s sharing this port must agree on it.
545    #[serde(default)]
546    pub ice_udp_mux_port: Option<u16>,
547    /// SDP generation compatibility mode.
548    #[serde(default)]
549    pub sdp_compatibility: SdpCompatibilityMode,
550    #[serde(skip, default)]
551    pub label: Option<String>,
552    #[serde(skip, default)]
553    pub cname: Option<String>,
554    /// Runtime handle for spawning internal tasks (ICE runner, DTLS, RTCP,
555    /// etc.). When set, ALL rustrtc `tokio::spawn` calls use this handle,
556    /// pinning media tasks to a dedicated runtime instead of the caller's.
557    /// Falls back to `Handle::current()` when `None` (backward-compatible).
558    #[serde(skip, default)]
559    pub runtime_handle: Option<tokio::runtime::Handle>,
560    /// Recording / tapping interceptors installed on every transceiver
561    /// created by this PC. Receiver interceptors fire on incoming RTP
562    /// (pre-depacketize); sender interceptors fire on outgoing RTP
563    /// (post seq/timestamp rewrite, pre-wire).
564    #[serde(skip, default)]
565    pub recorder_interceptors: RecorderInterceptors,
566}
567
568impl PartialEq for RtcConfiguration {
569    fn eq(&self, other: &Self) -> bool {
570        // Compare all fields except runtime_handle (Handle does not implement Eq).
571        self.ice_servers == other.ice_servers
572            && self.ice_transport_policy == other.ice_transport_policy
573            && self.bundle_policy == other.bundle_policy
574            && self.rtcp_mux_policy == other.rtcp_mux_policy
575            && self.certificates == other.certificates
576            && self.transport_mode == other.transport_mode
577            && self.nack_buffer_size == other.nack_buffer_size
578            && self.media_capabilities == other.media_capabilities
579            && self.external_ip == other.external_ip
580            && self.external_port == other.external_port
581            && self.bind_ip == other.bind_ip
582            && self.disable_ipv6 == other.disable_ipv6
583            && self.ssrc_start == other.ssrc_start
584            && self.stun_timeout == other.stun_timeout
585            && self.nomination_timeout == other.nomination_timeout
586            && self.ice_connection_timeout == other.ice_connection_timeout
587            && self.ice_disconnect_threshold == other.ice_disconnect_threshold
588            && self.ice_disconnect_grace == other.ice_disconnect_grace
589            && self.sctp_rto_initial == other.sctp_rto_initial
590            && self.sctp_rto_min == other.sctp_rto_min
591            && self.sctp_rto_max == other.sctp_rto_max
592            && self.sctp_max_association_retransmits == other.sctp_max_association_retransmits
593            && self.sctp_receive_window == other.sctp_receive_window
594            && self.sctp_heartbeat_interval == other.sctp_heartbeat_interval
595            && self.sctp_max_heartbeat_failures == other.sctp_max_heartbeat_failures
596            && self.sctp_max_tsn_retransmits == other.sctp_max_tsn_retransmits
597            && self.sctp_max_burst == other.sctp_max_burst
598            && self.sctp_max_cwnd == other.sctp_max_cwnd
599            && self.sctp_max_buffered_amount == other.sctp_max_buffered_amount
600            && self.dtls_buffer_size == other.dtls_buffer_size
601            && self.rtp_start_port == other.rtp_start_port
602            && self.rtp_end_port == other.rtp_end_port
603            && self.ice_gather_udp_hosts == other.ice_gather_udp_hosts
604            && self.tcp_port_range_start == other.tcp_port_range_start
605            && self.tcp_port_range_end == other.tcp_port_range_end
606            && self.enable_latching == other.enable_latching
607            && self.probation_max_packets == other.probation_max_packets
608            && self.enable_ice_lite == other.enable_ice_lite
609            && self.prefer_srflx_over_natted_host == other.prefer_srflx_over_natted_host
610            && self.enable_upnp == other.enable_upnp
611            && self.upnp_lease_duration == other.upnp_lease_duration
612            && self.upnp_discovery_timeout == other.upnp_discovery_timeout
613            && self.depacketizer_strategy == other.depacketizer_strategy
614            && self.rtp_buffer_capacity == other.rtp_buffer_capacity
615            && self.buffer_drop_strategy == other.buffer_drop_strategy
616            && self.buffer_stats_log_interval == other.buffer_stats_log_interval
617            && self.ice_tcp_policy == other.ice_tcp_policy
618            && self.ice_udp_mux == other.ice_udp_mux
619            && self.ice_udp_mux_port == other.ice_udp_mux_port
620            && self.sdp_compatibility == other.sdp_compatibility
621            && self.label == other.label
622            && self.cname == other.cname
623        // runtime_handle is intentionally omitted
624    }
625}
626
627impl Eq for RtcConfiguration {}
628
629impl Default for RtcConfiguration {
630    fn default() -> Self {
631        Self {
632            ice_servers: Vec::new(),
633            ice_transport_policy: IceTransportPolicy::default(),
634            bundle_policy: BundlePolicy::default(),
635            rtcp_mux_policy: RtcpMuxPolicy::default(),
636            certificates: Vec::new(),
637            transport_mode: TransportMode::default(),
638            nack_buffer_size: 200,
639            media_capabilities: None,
640            external_ip: None,
641            external_port: None,
642            bind_ip: None,
643            disable_ipv6: false,
644            ssrc_start: 10000,
645            stun_timeout: std::time::Duration::from_secs(5),
646            nomination_timeout: std::time::Duration::from_secs(10),
647            ice_connection_timeout: std::time::Duration::from_secs(30),
648            ice_disconnect_threshold: std::time::Duration::from_secs(5),
649            ice_disconnect_grace: std::time::Duration::from_secs(15),
650            sctp_rto_initial: std::time::Duration::from_secs(3),
651            sctp_rto_min: std::time::Duration::from_millis(200),
652            sctp_rto_max: std::time::Duration::from_secs(60),
653            sctp_max_association_retransmits: 20,
654            sctp_receive_window: 128 * 1024, // 128KB - reduced for lower memory footprint
655            sctp_heartbeat_interval: std::time::Duration::from_secs(15),
656            sctp_max_heartbeat_failures: 4,
657            sctp_max_tsn_retransmits: 8,
658            sctp_max_burst: 0,         // 0 = use default heuristic
659            sctp_max_cwnd: 256 * 1024, // 256 KB
660            sctp_max_buffered_amount: 256 * 1024, // 256 KB
661            dtls_buffer_size: 2048,
662            rtp_start_port: None,
663            rtp_end_port: None,
664            ice_gather_udp_hosts: true,
665            tcp_port_range_start: None,
666            tcp_port_range_end: None,
667            enable_latching: false,
668            probation_max_packets: None,
669            enable_ice_lite: false,
670            prefer_srflx_over_natted_host: false,
671            enable_upnp: default_enable_upnp(),
672            upnp_lease_duration: default_upnp_lease_duration(),
673            upnp_discovery_timeout: default_upnp_discovery_timeout(),
674            depacketizer_strategy: DepacketizerStrategy::default(),
675            rtp_buffer_capacity: default_rtp_buffer_capacity(),
676            buffer_drop_strategy: BufferDropStrategy::default(),
677            buffer_stats_log_interval: default_buffer_stats_log_interval(),
678            ice_tcp_policy: IceTcpPolicy::default(),
679            ice_udp_mux: false,
680            ice_udp_mux_port: None,
681            sdp_compatibility: SdpCompatibilityMode::default(),
682            label: None,
683            cname: None,
684            runtime_handle: None,
685            recorder_interceptors: RecorderInterceptors::default(),
686        }
687    }
688}
689
690pub struct RtcConfigurationBuilder {
691    inner: RtcConfiguration,
692}
693
694impl Default for RtcConfigurationBuilder {
695    fn default() -> Self {
696        Self::new()
697    }
698}
699
700/// Wrapper for interceptor lists so that `RtcConfiguration` can still derive
701/// `Debug / Clone / PartialEq / Eq` — the actual interceptor trait objects
702/// are opaque and only compared by count.
703#[derive(Clone, Default)]
704pub struct RecorderInterceptors {
705    pub receivers: Vec<Arc<dyn RtpReceiverInterceptor>>,
706    pub senders: Vec<Arc<dyn RtpSenderInterceptor>>,
707}
708
709impl Debug for RecorderInterceptors {
710    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
711        f.debug_struct("RecorderInterceptors")
712            .field("receivers_len", &self.receivers.len())
713            .field("senders_len", &self.senders.len())
714            .finish()
715    }
716}
717
718impl PartialEq for RecorderInterceptors {
719    fn eq(&self, other: &Self) -> bool {
720        self.receivers.len() == other.receivers.len()
721            && self.senders.len() == other.senders.len()
722    }
723}
724
725impl Eq for RecorderInterceptors {}
726
727impl RtcConfigurationBuilder {
728    pub fn new() -> Self {
729        Self {
730            inner: RtcConfiguration::default(),
731        }
732    }
733
734    pub fn enable_latching(mut self, enable: bool) -> Self {
735        self.inner.enable_latching = enable;
736        self
737    }
738
739    pub fn probation_max_packets(mut self, max: Option<u8>) -> Self {
740        self.inner.probation_max_packets = max;
741        self
742    }
743
744    pub fn enable_ice_lite(mut self, enable: bool) -> Self {
745        self.inner.enable_ice_lite = enable;
746        self
747    }
748
749    pub fn prefer_srflx_over_natted_host(mut self, enable: bool) -> Self {
750        self.inner.prefer_srflx_over_natted_host = enable;
751        self
752    }
753
754    pub fn enable_upnp(mut self, enable: bool) -> Self {
755        self.inner.enable_upnp = enable;
756        self
757    }
758
759    pub fn upnp_lease_duration(mut self, duration_secs: u32) -> Self {
760        self.inner.upnp_lease_duration = duration_secs;
761        self
762    }
763
764    pub fn upnp_discovery_timeout(mut self, timeout: std::time::Duration) -> Self {
765        self.inner.upnp_discovery_timeout = timeout;
766        self
767    }
768
769    pub fn ice_server(mut self, server: IceServer) -> Self {
770        self.inner.ice_servers.push(server);
771        self
772    }
773
774    pub fn ice_transport_policy(mut self, policy: IceTransportPolicy) -> Self {
775        self.inner.ice_transport_policy = policy;
776        self
777    }
778
779    pub fn bundle_policy(mut self, policy: BundlePolicy) -> Self {
780        self.inner.bundle_policy = policy;
781        self
782    }
783
784    pub fn rtcp_mux_policy(mut self, policy: RtcpMuxPolicy) -> Self {
785        self.inner.rtcp_mux_policy = policy;
786        self
787    }
788
789    pub fn certificate(mut self, cert: CertificateConfig) -> Self {
790        self.inner.certificates.push(cert);
791        self
792    }
793
794    pub fn transport_mode(mut self, mode: TransportMode) -> Self {
795        self.inner.transport_mode = mode;
796        self
797    }
798
799    pub fn media_capabilities(mut self, capabilities: MediaCapabilities) -> Self {
800        self.inner.media_capabilities = Some(capabilities);
801        self
802    }
803
804    pub fn external_ip(mut self, ip: String) -> Self {
805        self.inner.external_ip = Some(ip);
806        self
807    }
808
809    pub fn external_port(mut self, port: u16) -> Self {
810        self.inner.external_port = Some(port);
811        self
812    }
813
814    pub fn bind_ip(mut self, ip: String) -> Self {
815        self.inner.bind_ip = Some(ip);
816        self
817    }
818
819    pub fn disable_ipv6(mut self, disable: bool) -> Self {
820        self.inner.disable_ipv6 = disable;
821        self
822    }
823
824    pub fn ssrc_start(mut self, start: u32) -> Self {
825        self.inner.ssrc_start = start;
826        self
827    }
828
829    pub fn stun_timeout(mut self, timeout: std::time::Duration) -> Self {
830        self.inner.stun_timeout = timeout;
831        self
832    }
833
834    pub fn nomination_timeout(mut self, timeout: std::time::Duration) -> Self {
835        self.inner.nomination_timeout = timeout;
836        self
837    }
838
839    pub fn rtp_port_range(mut self, start: u16, end: u16) -> Self {
840        self.inner.rtp_start_port = Some(start);
841        self.inner.rtp_end_port = Some(end);
842        self
843    }
844
845    pub fn ice_gather_udp_hosts(mut self, enable: bool) -> Self {
846        self.inner.ice_gather_udp_hosts = enable;
847        self
848    }
849
850    pub fn tcp_port_range(mut self, start: u16, end: u16) -> Self {
851        self.inner.tcp_port_range_start = Some(start);
852        self.inner.tcp_port_range_end = Some(end);
853        self
854    }
855
856    pub fn dtls_buffer_size(mut self, size: usize) -> Self {
857        self.inner.dtls_buffer_size = size;
858        self
859    }
860
861    pub fn sctp_rto_initial(mut self, duration: std::time::Duration) -> Self {
862        self.inner.sctp_rto_initial = duration;
863        self
864    }
865
866    pub fn sctp_rto_min(mut self, duration: std::time::Duration) -> Self {
867        self.inner.sctp_rto_min = duration;
868        self
869    }
870
871    pub fn sctp_rto_max(mut self, duration: std::time::Duration) -> Self {
872        self.inner.sctp_rto_max = duration;
873        self
874    }
875
876    pub fn sctp_max_association_retransmits(mut self, count: u32) -> Self {
877        self.inner.sctp_max_association_retransmits = count;
878        self
879    }
880
881    pub fn sctp_receive_window(mut self, size: usize) -> Self {
882        self.inner.sctp_receive_window = size;
883        self
884    }
885
886    pub fn sctp_heartbeat_interval(mut self, duration: std::time::Duration) -> Self {
887        self.inner.sctp_heartbeat_interval = duration;
888        self
889    }
890
891    pub fn sctp_max_heartbeat_failures(mut self, count: u32) -> Self {
892        self.inner.sctp_max_heartbeat_failures = count;
893        self
894    }
895
896    /// Set the maximum burst size for SCTP in number of MTU-sized packets.
897    /// 0 means use the default heuristic (16 packets normal, 4 in recovery).
898    /// For rate-limited TURN relays, a value of 2-4 can reduce burst-induced
899    /// packet loss.
900    pub fn sctp_max_burst(mut self, packets: usize) -> Self {
901        self.inner.sctp_max_burst = packets;
902        self
903    }
904
905    /// Set the maximum congestion window size in bytes.
906    /// Default is 256 KB. For high-latency TURN relays, consider 512KB-1MB.
907    pub fn sctp_max_cwnd(mut self, size: usize) -> Self {
908        self.inner.sctp_max_cwnd = size;
909        self
910    }
911
912    /// Per-data-channel send-side buffered-amount limit in bytes.
913    /// Default is 256 KB. Set to 0 for unbounded (compatibility with pre-0.3.114
914    /// behavior that had no gate). A non-zero limit bounds the sum of in-flight
915    /// and queued payload bytes per data channel, blocking `send()`/`send_text()`
916    /// until the buffer drains below the threshold.
917    pub fn sctp_max_buffered_amount(mut self, bytes: usize) -> Self {
918        self.inner.sctp_max_buffered_amount = bytes;
919        self
920    }
921
922    pub fn ice_connection_timeout(mut self, timeout: std::time::Duration) -> Self {
923        self.inner.ice_connection_timeout = timeout;
924        self
925    }
926
927    pub fn ice_disconnect_threshold(mut self, threshold: std::time::Duration) -> Self {
928        self.inner.ice_disconnect_threshold = threshold;
929        self
930    }
931
932    pub fn ice_disconnect_grace(mut self, grace: std::time::Duration) -> Self {
933        self.inner.ice_disconnect_grace = grace;
934        self
935    }
936
937    pub fn rtp_buffer_capacity(mut self, capacity: usize) -> Self {
938        self.inner.rtp_buffer_capacity = capacity;
939        self
940    }
941
942    pub fn buffer_drop_strategy(mut self, strategy: BufferDropStrategy) -> Self {
943        self.inner.buffer_drop_strategy = strategy;
944        self
945    }
946
947    pub fn buffer_stats_log_interval(mut self, interval: std::time::Duration) -> Self {
948        self.inner.buffer_stats_log_interval = interval;
949        self
950    }
951
952    pub fn ice_tcp_policy(mut self, policy: IceTcpPolicy) -> Self {
953        self.inner.ice_tcp_policy = policy;
954        self
955    }
956
957    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
958    /// Requires `ice_udp_mux_port` to also be set.
959    pub fn ice_udp_mux(mut self, enable: bool) -> Self {
960        self.inner.ice_udp_mux = enable;
961        self
962    }
963
964    /// Set the shared UDP mux port. Must be set when `ice_udp_mux` is enabled.
965    pub fn ice_udp_mux_port(mut self, port: u16) -> Self {
966        self.inner.ice_udp_mux_port = Some(port);
967        self
968    }
969
970    pub fn sdp_compatibility(mut self, mode: SdpCompatibilityMode) -> Self {
971        self.inner.sdp_compatibility = mode;
972        self
973    }
974
975    pub fn cname(mut self, cname: String) -> Self {
976        self.inner.cname = Some(cname);
977        self
978    }
979
980    /// Append a receiver interceptor (fires on every incoming RTP packet,
981    /// pre-depacketize).
982    pub fn receiver_interceptor(
983        mut self,
984        interceptor: Arc<dyn RtpReceiverInterceptor>,
985    ) -> Self {
986        self.inner.recorder_interceptors.receivers.push(interceptor);
987        self
988    }
989
990    /// Append a sender interceptor (fires on every outgoing RTP packet,
991    /// post seq/timestamp rewrite).
992    pub fn sender_interceptor(
993        mut self,
994        interceptor: Arc<dyn RtpSenderInterceptor>,
995    ) -> Self {
996        self.inner.recorder_interceptors.senders.push(interceptor);
997        self
998    }
999
1000    /// Set the runtime handle for spawning internal tasks (ICE runner, DTLS,
1001    /// RTCP loops, etc.). When set, all rustrtc-internal tokio::spawn calls
1002    /// go through this runtime instead of the ambient tokio runtime.
1003    pub fn runtime_handle(mut self, handle: tokio::runtime::Handle) -> Self {
1004        self.inner.runtime_handle = Some(handle);
1005        self
1006    }
1007
1008    pub fn build(self) -> RtcConfiguration {
1009        self.inner
1010    }
1011}
1012
1013impl From<RtcConfigurationBuilder> for RtcConfiguration {
1014    fn from(builder: RtcConfigurationBuilder) -> Self {
1015        builder.build()
1016    }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::*;
1022    use std::time::Duration;
1023
1024    #[test]
1025    fn test_rtc_configuration_defaults() {
1026        let config = RtcConfiguration::default();
1027        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
1028        assert_eq!(config.ice_disconnect_threshold, Duration::from_secs(5));
1029        assert_eq!(config.ice_disconnect_grace, Duration::from_secs(15));
1030        assert_eq!(config.sctp_rto_initial, Duration::from_secs(3));
1031        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
1032        assert_eq!(config.sctp_rto_max, Duration::from_secs(60));
1033        assert_eq!(config.sctp_max_association_retransmits, 20);
1034        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(15));
1035        assert_eq!(config.sctp_max_heartbeat_failures, 4);
1036        assert_eq!(config.sctp_max_burst, 0);
1037        assert_eq!(config.sctp_max_cwnd, 256 * 1024);
1038        assert_eq!(config.rtp_buffer_capacity, 100);
1039        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropNew);
1040        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(10));
1041    }
1042
1043    #[test]
1044    fn test_rtc_configuration_builder() {
1045        let config = RtcConfigurationBuilder::new()
1046            .stun_timeout(Duration::from_secs(10))
1047            .build();
1048        assert_eq!(config.stun_timeout, Duration::from_secs(10));
1049        // Verify other defaults are still there
1050        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
1051    }
1052
1053    #[test]
1054    fn test_buffer_config_builder() {
1055        let config = RtcConfigurationBuilder::new()
1056            .rtp_buffer_capacity(200)
1057            .buffer_drop_strategy(BufferDropStrategy::DropOldest)
1058            .buffer_stats_log_interval(Duration::from_secs(5))
1059            .build();
1060        assert_eq!(config.rtp_buffer_capacity, 200);
1061        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropOldest);
1062        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(5));
1063    }
1064
1065    #[test]
1066    fn test_sctp_builder_methods() {
1067        let config = RtcConfigurationBuilder::new()
1068            .sctp_rto_initial(Duration::from_millis(500))
1069            .sctp_rto_min(Duration::from_millis(200))
1070            .sctp_rto_max(Duration::from_secs(10))
1071            .sctp_max_association_retransmits(30)
1072            .sctp_receive_window(512 * 1024)
1073            .sctp_heartbeat_interval(Duration::from_secs(10))
1074            .sctp_max_heartbeat_failures(8)
1075            .sctp_max_burst(4)
1076            .sctp_max_cwnd(512 * 1024)
1077            .ice_connection_timeout(Duration::from_secs(60))
1078            .build();
1079
1080        assert_eq!(config.sctp_rto_initial, Duration::from_millis(500));
1081        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
1082        assert_eq!(config.sctp_rto_max, Duration::from_secs(10));
1083        assert_eq!(config.sctp_max_association_retransmits, 30);
1084        assert_eq!(config.sctp_receive_window, 512 * 1024);
1085        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(10));
1086        assert_eq!(config.sctp_max_heartbeat_failures, 8);
1087        assert_eq!(config.sctp_max_burst, 4);
1088        assert_eq!(config.sctp_max_cwnd, 512 * 1024);
1089        assert_eq!(config.ice_connection_timeout, Duration::from_secs(60));
1090    }
1091
1092    #[test]
1093    fn test_turn_optimized_config() {
1094        // Verify a TURN-optimized configuration can be expressed cleanly
1095        let config = RtcConfigurationBuilder::new()
1096            .sctp_rto_initial(Duration::from_millis(500))
1097            .sctp_rto_min(Duration::from_millis(100))
1098            .sctp_rto_max(Duration::from_secs(10))
1099            .sctp_max_association_retransmits(30)
1100            .sctp_max_heartbeat_failures(8)
1101            .sctp_max_burst(4)
1102            .stun_timeout(Duration::from_secs(10))
1103            .nomination_timeout(Duration::from_secs(20))
1104            .build();
1105
1106        // Verify the TURN-optimized values are more aggressive than defaults
1107        let defaults = RtcConfiguration::default();
1108        assert!(config.sctp_rto_initial < defaults.sctp_rto_initial);
1109        assert!(config.sctp_rto_min < defaults.sctp_rto_min);
1110        assert!(config.sctp_rto_max < defaults.sctp_rto_max);
1111        assert!(
1112            config.sctp_max_association_retransmits > defaults.sctp_max_association_retransmits
1113        );
1114        assert!(config.sctp_max_heartbeat_failures > defaults.sctp_max_heartbeat_failures);
1115        assert!(config.sctp_max_burst > 0); // Explicit burst limit vs. heuristic
1116    }
1117
1118    #[test]
1119    fn test_external_port_defaults() {
1120        let config = RtcConfiguration::default();
1121        assert_eq!(config.external_port, None);
1122    }
1123
1124    #[test]
1125    fn test_external_port_builder() {
1126        let config = RtcConfigurationBuilder::new().external_port(30000).build();
1127        assert_eq!(config.external_port, Some(30000));
1128    }
1129
1130    #[test]
1131    fn test_external_port_with_external_ip_builder() {
1132        let config = RtcConfigurationBuilder::new()
1133            .external_ip("203.0.113.5".to_string())
1134            .external_port(30000)
1135            .build();
1136        assert_eq!(config.external_ip, Some("203.0.113.5".to_string()));
1137        assert_eq!(config.external_port, Some(30000));
1138    }
1139
1140    #[test]
1141    fn test_upnp_defaults() {
1142        let config = RtcConfiguration::default();
1143        assert!(!config.enable_upnp, "UPnP should be disabled by default");
1144        assert_eq!(config.upnp_lease_duration, 3600);
1145    }
1146
1147    #[test]
1148    fn test_upnp_builder_methods() {
1149        let config = RtcConfigurationBuilder::new()
1150            .enable_upnp(false)
1151            .upnp_lease_duration(7200)
1152            .build();
1153        assert!(!config.enable_upnp);
1154        assert_eq!(config.upnp_lease_duration, 7200);
1155    }
1156
1157    #[test]
1158    fn test_upnp_optimized_config() {
1159        let config = RtcConfigurationBuilder::new()
1160            .enable_upnp(true)
1161            .upnp_lease_duration(1800)
1162            .build();
1163
1164        assert!(config.enable_upnp);
1165        assert_eq!(config.upnp_lease_duration, 1800);
1166
1167        // Verify defaults remain for other options
1168        let defaults = RtcConfiguration::default();
1169        assert_eq!(
1170            config.ice_connection_timeout,
1171            defaults.ice_connection_timeout
1172        );
1173    }
1174
1175    #[test]
1176    fn test_ice_udp_mux_defaults() {
1177        let config = RtcConfiguration::default();
1178        assert!(
1179            !config.ice_udp_mux,
1180            "ICE UDP mux should be disabled by default"
1181        );
1182        assert_eq!(config.ice_udp_mux_port, None);
1183    }
1184
1185    #[test]
1186    fn test_ice_udp_mux_builder_methods() {
1187        let config = RtcConfigurationBuilder::new()
1188            .ice_udp_mux(true)
1189            .ice_udp_mux_port(30500)
1190            .build();
1191        assert!(config.ice_udp_mux);
1192        assert_eq!(config.ice_udp_mux_port, Some(30500));
1193    }
1194}