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
414/// Primary configuration for a `PeerConnection`.
415#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
416pub struct RtcConfiguration {
417    pub ice_servers: Vec<IceServer>,
418    pub ice_transport_policy: IceTransportPolicy,
419    pub bundle_policy: BundlePolicy,
420    pub rtcp_mux_policy: RtcpMuxPolicy,
421    pub certificates: Vec<CertificateConfig>,
422    pub transport_mode: TransportMode,
423    pub nack_buffer_size: usize,
424    pub media_capabilities: Option<MediaCapabilities>,
425    /// Override the advertised IP address in SDP (for NAT traversal).
426    /// When set, the `c=`, `o=`, and candidate addresses in the SDP will
427    /// use this IP instead of the local bind IP. The local bind address is
428    /// stored in `related_address` on the candidate.
429    pub external_ip: Option<String>,
430    /// Override the advertised port in SDP `m=` line and candidates
431    /// (for NAT port forwarding).
432    ///
433    /// When set, the SDP will advertise this port instead of the local
434    /// bind port. This is useful when you have configured NAT port
435    /// forwarding (e.g. external port 30000 → local port 20000) and need
436    /// the remote peer to send RTP to the external port.
437    ///
438    /// Works independently or combined with `external_ip`.
439    /// Only applies in RTP/SRTP direct mode (`TransportMode::Rtp` /
440    /// `TransportMode::Srtp`). Not used in WebRTC mode.
441    pub external_port: Option<u16>,
442    pub bind_ip: Option<String>,
443    pub disable_ipv6: bool,
444    pub ssrc_start: u32,
445    pub stun_timeout: std::time::Duration,
446    /// Timeout for the ICE nomination binding check (USE-CANDIDATE).
447    /// This should be larger than `stun_timeout` to allow more retransmissions
448    /// and reduce the probability of nomination failures under packet loss.
449    pub nomination_timeout: std::time::Duration,
450    pub ice_connection_timeout: std::time::Duration,
451    pub sctp_rto_initial: std::time::Duration,
452    pub sctp_rto_min: std::time::Duration,
453    pub sctp_rto_max: std::time::Duration,
454    pub sctp_max_association_retransmits: u32,
455    pub sctp_receive_window: usize,
456    pub sctp_heartbeat_interval: std::time::Duration,
457    pub sctp_max_heartbeat_failures: u32,
458    pub sctp_max_tsn_retransmits: u32,
459    pub sctp_max_burst: usize,
460    pub sctp_max_cwnd: usize,
461    pub dtls_buffer_size: usize,
462    pub rtp_start_port: Option<u16>,
463    pub rtp_end_port: Option<u16>,
464    pub ice_gather_udp_hosts: bool,
465    pub tcp_port_range_start: Option<u16>,
466    pub tcp_port_range_end: Option<u16>,
467    pub enable_latching: bool,
468    pub probation_max_packets: Option<u8>,
469    pub enable_ice_lite: bool,
470    /// When true, demote host candidates with private (RFC 1918) local IPs
471    /// below server-reflexive candidates in the connectivity check ordering.
472    /// This avoids DTLS handshake failures behind NATs where a host candidate
473    /// can pass a single STUN binding check but cannot sustain bidirectional
474    /// DTLS traffic.  Same-LAN pairs (both sides private) are not affected.
475    /// Default: false (standard RFC 5245 behavior).
476    #[serde(default)]
477    pub prefer_srflx_over_natted_host: bool,
478    /// Enable UPnP IGD for automatic port mapping
479    #[serde(default = "default_enable_upnp")]
480    pub enable_upnp: bool,
481    /// UPnP port mapping lease duration in seconds
482    #[serde(default = "default_upnp_lease_duration")]
483    pub upnp_lease_duration: u32,
484    #[serde(skip, default)]
485    pub depacketizer_strategy: DepacketizerStrategy,
486    #[serde(default = "default_rtp_buffer_capacity")]
487    pub rtp_buffer_capacity: usize,
488    #[serde(default)]
489    pub buffer_drop_strategy: BufferDropStrategy,
490    #[serde(default = "default_buffer_stats_log_interval")]
491    pub buffer_stats_log_interval: std::time::Duration,
492    /// Controls ICE TCP candidate support (RFC 6544).
493    /// Default: Disabled — only UDP candidates are gathered and used.
494    #[serde(default)]
495    pub ice_tcp_policy: IceTcpPolicy,
496    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
497    ///
498    /// When `true`, multiple `PeerConnection`s share one `UdpSocket` bound to
499    /// `ice_udp_mux_port`. Incoming UDP packets are demultiplexed by the server
500    /// ufrag embedded in the first STUN Binding Request's `USERNAME` attribute,
501    /// and — once a pair is established — by the remote source address.
502    ///
503    /// Requires `ice_udp_mux_port` to be set. Useful for SFU/WHEP deployments
504    /// that need to advertise a single public UDP port for many sessions.
505    #[serde(default)]
506    pub ice_udp_mux: bool,
507    /// UDP port to bind the shared mux socket on. Required when `ice_udp_mux`
508    /// is enabled. All `PeerConnection`s sharing this port must agree on it.
509    #[serde(default)]
510    pub ice_udp_mux_port: Option<u16>,
511    /// SDP generation compatibility mode.
512    #[serde(default)]
513    pub sdp_compatibility: SdpCompatibilityMode,
514    #[serde(skip, default)]
515    pub label: Option<String>,
516    #[serde(skip, default)]
517    pub cname: Option<String>,
518    /// Recording / tapping interceptors installed on every transceiver
519    /// created by this PC. Receiver interceptors fire on incoming RTP
520    /// (pre-depacketize); sender interceptors fire on outgoing RTP
521    /// (post seq/timestamp rewrite, pre-wire).
522    #[serde(skip, default)]
523    pub recorder_interceptors: RecorderInterceptors,
524}
525
526impl Default for RtcConfiguration {
527    fn default() -> Self {
528        Self {
529            ice_servers: Vec::new(),
530            ice_transport_policy: IceTransportPolicy::default(),
531            bundle_policy: BundlePolicy::default(),
532            rtcp_mux_policy: RtcpMuxPolicy::default(),
533            certificates: Vec::new(),
534            transport_mode: TransportMode::default(),
535            nack_buffer_size: 200,
536            media_capabilities: None,
537            external_ip: None,
538            external_port: None,
539            bind_ip: None,
540            disable_ipv6: false,
541            ssrc_start: 10000,
542            stun_timeout: std::time::Duration::from_secs(5),
543            nomination_timeout: std::time::Duration::from_secs(10),
544            ice_connection_timeout: std::time::Duration::from_secs(30),
545            sctp_rto_initial: std::time::Duration::from_secs(3),
546            sctp_rto_min: std::time::Duration::from_secs(1),
547            sctp_rto_max: std::time::Duration::from_secs(60),
548            sctp_max_association_retransmits: 20,
549            sctp_receive_window: 128 * 1024, // 128KB - reduced for lower memory footprint
550            sctp_heartbeat_interval: std::time::Duration::from_secs(15),
551            sctp_max_heartbeat_failures: 4,
552            sctp_max_tsn_retransmits: 8,
553            sctp_max_burst: 0,         // 0 = use default heuristic
554            sctp_max_cwnd: 256 * 1024, // 256 KB
555            dtls_buffer_size: 2048,
556            rtp_start_port: None,
557            rtp_end_port: None,
558            ice_gather_udp_hosts: true,
559            tcp_port_range_start: None,
560            tcp_port_range_end: None,
561            enable_latching: false,
562            probation_max_packets: None,
563            enable_ice_lite: false,
564            prefer_srflx_over_natted_host: false,
565            enable_upnp: default_enable_upnp(),
566            upnp_lease_duration: default_upnp_lease_duration(),
567            depacketizer_strategy: DepacketizerStrategy::default(),
568            rtp_buffer_capacity: default_rtp_buffer_capacity(),
569            buffer_drop_strategy: BufferDropStrategy::default(),
570            buffer_stats_log_interval: default_buffer_stats_log_interval(),
571            ice_tcp_policy: IceTcpPolicy::default(),
572            ice_udp_mux: false,
573            ice_udp_mux_port: None,
574            sdp_compatibility: SdpCompatibilityMode::default(),
575            label: None,
576            cname: None,
577            recorder_interceptors: RecorderInterceptors::default(),
578        }
579    }
580}
581
582pub struct RtcConfigurationBuilder {
583    inner: RtcConfiguration,
584}
585
586impl Default for RtcConfigurationBuilder {
587    fn default() -> Self {
588        Self::new()
589    }
590}
591
592/// Wrapper for interceptor lists so that `RtcConfiguration` can still derive
593/// `Debug / Clone / PartialEq / Eq` — the actual interceptor trait objects
594/// are opaque and only compared by count.
595#[derive(Clone, Default)]
596pub struct RecorderInterceptors {
597    pub receivers: Vec<Arc<dyn RtpReceiverInterceptor>>,
598    pub senders: Vec<Arc<dyn RtpSenderInterceptor>>,
599}
600
601impl Debug for RecorderInterceptors {
602    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
603        f.debug_struct("RecorderInterceptors")
604            .field("receivers_len", &self.receivers.len())
605            .field("senders_len", &self.senders.len())
606            .finish()
607    }
608}
609
610impl PartialEq for RecorderInterceptors {
611    fn eq(&self, other: &Self) -> bool {
612        self.receivers.len() == other.receivers.len()
613            && self.senders.len() == other.senders.len()
614    }
615}
616
617impl Eq for RecorderInterceptors {}
618
619impl RtcConfigurationBuilder {
620    pub fn new() -> Self {
621        Self {
622            inner: RtcConfiguration::default(),
623        }
624    }
625
626    pub fn enable_latching(mut self, enable: bool) -> Self {
627        self.inner.enable_latching = enable;
628        self
629    }
630
631    pub fn probation_max_packets(mut self, max: Option<u8>) -> Self {
632        self.inner.probation_max_packets = max;
633        self
634    }
635
636    pub fn enable_ice_lite(mut self, enable: bool) -> Self {
637        self.inner.enable_ice_lite = enable;
638        self
639    }
640
641    pub fn prefer_srflx_over_natted_host(mut self, enable: bool) -> Self {
642        self.inner.prefer_srflx_over_natted_host = enable;
643        self
644    }
645
646    pub fn enable_upnp(mut self, enable: bool) -> Self {
647        self.inner.enable_upnp = enable;
648        self
649    }
650
651    pub fn upnp_lease_duration(mut self, duration_secs: u32) -> Self {
652        self.inner.upnp_lease_duration = duration_secs;
653        self
654    }
655
656    pub fn ice_server(mut self, server: IceServer) -> Self {
657        self.inner.ice_servers.push(server);
658        self
659    }
660
661    pub fn ice_transport_policy(mut self, policy: IceTransportPolicy) -> Self {
662        self.inner.ice_transport_policy = policy;
663        self
664    }
665
666    pub fn bundle_policy(mut self, policy: BundlePolicy) -> Self {
667        self.inner.bundle_policy = policy;
668        self
669    }
670
671    pub fn rtcp_mux_policy(mut self, policy: RtcpMuxPolicy) -> Self {
672        self.inner.rtcp_mux_policy = policy;
673        self
674    }
675
676    pub fn certificate(mut self, cert: CertificateConfig) -> Self {
677        self.inner.certificates.push(cert);
678        self
679    }
680
681    pub fn transport_mode(mut self, mode: TransportMode) -> Self {
682        self.inner.transport_mode = mode;
683        self
684    }
685
686    pub fn media_capabilities(mut self, capabilities: MediaCapabilities) -> Self {
687        self.inner.media_capabilities = Some(capabilities);
688        self
689    }
690
691    pub fn external_ip(mut self, ip: String) -> Self {
692        self.inner.external_ip = Some(ip);
693        self
694    }
695
696    pub fn external_port(mut self, port: u16) -> Self {
697        self.inner.external_port = Some(port);
698        self
699    }
700
701    pub fn bind_ip(mut self, ip: String) -> Self {
702        self.inner.bind_ip = Some(ip);
703        self
704    }
705
706    pub fn disable_ipv6(mut self, disable: bool) -> Self {
707        self.inner.disable_ipv6 = disable;
708        self
709    }
710
711    pub fn ssrc_start(mut self, start: u32) -> Self {
712        self.inner.ssrc_start = start;
713        self
714    }
715
716    pub fn stun_timeout(mut self, timeout: std::time::Duration) -> Self {
717        self.inner.stun_timeout = timeout;
718        self
719    }
720
721    pub fn nomination_timeout(mut self, timeout: std::time::Duration) -> Self {
722        self.inner.nomination_timeout = timeout;
723        self
724    }
725
726    pub fn rtp_port_range(mut self, start: u16, end: u16) -> Self {
727        self.inner.rtp_start_port = Some(start);
728        self.inner.rtp_end_port = Some(end);
729        self
730    }
731
732    pub fn ice_gather_udp_hosts(mut self, enable: bool) -> Self {
733        self.inner.ice_gather_udp_hosts = enable;
734        self
735    }
736
737    pub fn tcp_port_range(mut self, start: u16, end: u16) -> Self {
738        self.inner.tcp_port_range_start = Some(start);
739        self.inner.tcp_port_range_end = Some(end);
740        self
741    }
742
743    pub fn dtls_buffer_size(mut self, size: usize) -> Self {
744        self.inner.dtls_buffer_size = size;
745        self
746    }
747
748    pub fn sctp_rto_initial(mut self, duration: std::time::Duration) -> Self {
749        self.inner.sctp_rto_initial = duration;
750        self
751    }
752
753    pub fn sctp_rto_min(mut self, duration: std::time::Duration) -> Self {
754        self.inner.sctp_rto_min = duration;
755        self
756    }
757
758    pub fn sctp_rto_max(mut self, duration: std::time::Duration) -> Self {
759        self.inner.sctp_rto_max = duration;
760        self
761    }
762
763    pub fn sctp_max_association_retransmits(mut self, count: u32) -> Self {
764        self.inner.sctp_max_association_retransmits = count;
765        self
766    }
767
768    pub fn sctp_receive_window(mut self, size: usize) -> Self {
769        self.inner.sctp_receive_window = size;
770        self
771    }
772
773    pub fn sctp_heartbeat_interval(mut self, duration: std::time::Duration) -> Self {
774        self.inner.sctp_heartbeat_interval = duration;
775        self
776    }
777
778    pub fn sctp_max_heartbeat_failures(mut self, count: u32) -> Self {
779        self.inner.sctp_max_heartbeat_failures = count;
780        self
781    }
782
783    /// Set the maximum burst size for SCTP in number of MTU-sized packets.
784    /// 0 means use the default heuristic (16 packets normal, 4 in recovery).
785    /// For rate-limited TURN relays, a value of 2-4 can reduce burst-induced
786    /// packet loss.
787    pub fn sctp_max_burst(mut self, packets: usize) -> Self {
788        self.inner.sctp_max_burst = packets;
789        self
790    }
791
792    /// Set the maximum congestion window size in bytes.
793    /// Default is 256 KB. For high-latency TURN relays, consider 512KB-1MB.
794    pub fn sctp_max_cwnd(mut self, size: usize) -> Self {
795        self.inner.sctp_max_cwnd = size;
796        self
797    }
798
799    pub fn ice_connection_timeout(mut self, timeout: std::time::Duration) -> Self {
800        self.inner.ice_connection_timeout = timeout;
801        self
802    }
803
804    pub fn rtp_buffer_capacity(mut self, capacity: usize) -> Self {
805        self.inner.rtp_buffer_capacity = capacity;
806        self
807    }
808
809    pub fn buffer_drop_strategy(mut self, strategy: BufferDropStrategy) -> Self {
810        self.inner.buffer_drop_strategy = strategy;
811        self
812    }
813
814    pub fn buffer_stats_log_interval(mut self, interval: std::time::Duration) -> Self {
815        self.inner.buffer_stats_log_interval = interval;
816        self
817    }
818
819    pub fn ice_tcp_policy(mut self, policy: IceTcpPolicy) -> Self {
820        self.inner.ice_tcp_policy = policy;
821        self
822    }
823
824    /// Enable process-wide shared ICE UDP socket (single-port multiplexing).
825    /// Requires `ice_udp_mux_port` to also be set.
826    pub fn ice_udp_mux(mut self, enable: bool) -> Self {
827        self.inner.ice_udp_mux = enable;
828        self
829    }
830
831    /// Set the shared UDP mux port. Must be set when `ice_udp_mux` is enabled.
832    pub fn ice_udp_mux_port(mut self, port: u16) -> Self {
833        self.inner.ice_udp_mux_port = Some(port);
834        self
835    }
836
837    pub fn sdp_compatibility(mut self, mode: SdpCompatibilityMode) -> Self {
838        self.inner.sdp_compatibility = mode;
839        self
840    }
841
842    pub fn cname(mut self, cname: String) -> Self {
843        self.inner.cname = Some(cname);
844        self
845    }
846
847    /// Append a receiver interceptor (fires on every incoming RTP packet,
848    /// pre-depacketize).
849    pub fn receiver_interceptor(
850        mut self,
851        interceptor: Arc<dyn RtpReceiverInterceptor>,
852    ) -> Self {
853        self.inner.recorder_interceptors.receivers.push(interceptor);
854        self
855    }
856
857    /// Append a sender interceptor (fires on every outgoing RTP packet,
858    /// post seq/timestamp rewrite).
859    pub fn sender_interceptor(
860        mut self,
861        interceptor: Arc<dyn RtpSenderInterceptor>,
862    ) -> Self {
863        self.inner.recorder_interceptors.senders.push(interceptor);
864        self
865    }
866
867    pub fn build(self) -> RtcConfiguration {
868        self.inner
869    }
870}
871
872impl From<RtcConfigurationBuilder> for RtcConfiguration {
873    fn from(builder: RtcConfigurationBuilder) -> Self {
874        builder.build()
875    }
876}
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881    use std::time::Duration;
882
883    #[test]
884    fn test_rtc_configuration_defaults() {
885        let config = RtcConfiguration::default();
886        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
887        assert_eq!(config.sctp_rto_initial, Duration::from_secs(3));
888        assert_eq!(config.sctp_rto_min, Duration::from_secs(1));
889        assert_eq!(config.sctp_rto_max, Duration::from_secs(60));
890        assert_eq!(config.sctp_max_association_retransmits, 20);
891        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(15));
892        assert_eq!(config.sctp_max_heartbeat_failures, 4);
893        assert_eq!(config.sctp_max_burst, 0);
894        assert_eq!(config.sctp_max_cwnd, 256 * 1024);
895        assert_eq!(config.rtp_buffer_capacity, 100);
896        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropNew);
897        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(10));
898    }
899
900    #[test]
901    fn test_rtc_configuration_builder() {
902        let config = RtcConfigurationBuilder::new()
903            .stun_timeout(Duration::from_secs(10))
904            .build();
905        assert_eq!(config.stun_timeout, Duration::from_secs(10));
906        // Verify other defaults are still there
907        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
908    }
909
910    #[test]
911    fn test_buffer_config_builder() {
912        let config = RtcConfigurationBuilder::new()
913            .rtp_buffer_capacity(200)
914            .buffer_drop_strategy(BufferDropStrategy::DropOldest)
915            .buffer_stats_log_interval(Duration::from_secs(5))
916            .build();
917        assert_eq!(config.rtp_buffer_capacity, 200);
918        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropOldest);
919        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(5));
920    }
921
922    #[test]
923    fn test_sctp_builder_methods() {
924        let config = RtcConfigurationBuilder::new()
925            .sctp_rto_initial(Duration::from_millis(500))
926            .sctp_rto_min(Duration::from_millis(200))
927            .sctp_rto_max(Duration::from_secs(10))
928            .sctp_max_association_retransmits(30)
929            .sctp_receive_window(512 * 1024)
930            .sctp_heartbeat_interval(Duration::from_secs(10))
931            .sctp_max_heartbeat_failures(8)
932            .sctp_max_burst(4)
933            .sctp_max_cwnd(512 * 1024)
934            .ice_connection_timeout(Duration::from_secs(60))
935            .build();
936
937        assert_eq!(config.sctp_rto_initial, Duration::from_millis(500));
938        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
939        assert_eq!(config.sctp_rto_max, Duration::from_secs(10));
940        assert_eq!(config.sctp_max_association_retransmits, 30);
941        assert_eq!(config.sctp_receive_window, 512 * 1024);
942        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(10));
943        assert_eq!(config.sctp_max_heartbeat_failures, 8);
944        assert_eq!(config.sctp_max_burst, 4);
945        assert_eq!(config.sctp_max_cwnd, 512 * 1024);
946        assert_eq!(config.ice_connection_timeout, Duration::from_secs(60));
947    }
948
949    #[test]
950    fn test_turn_optimized_config() {
951        // Verify a TURN-optimized configuration can be expressed cleanly
952        let config = RtcConfigurationBuilder::new()
953            .sctp_rto_initial(Duration::from_millis(500))
954            .sctp_rto_min(Duration::from_millis(200))
955            .sctp_rto_max(Duration::from_secs(10))
956            .sctp_max_association_retransmits(30)
957            .sctp_max_heartbeat_failures(8)
958            .sctp_max_burst(4)
959            .stun_timeout(Duration::from_secs(10))
960            .nomination_timeout(Duration::from_secs(20))
961            .build();
962
963        // Verify the TURN-optimized values are more aggressive than defaults
964        let defaults = RtcConfiguration::default();
965        assert!(config.sctp_rto_initial < defaults.sctp_rto_initial);
966        assert!(config.sctp_rto_min < defaults.sctp_rto_min);
967        assert!(config.sctp_rto_max < defaults.sctp_rto_max);
968        assert!(
969            config.sctp_max_association_retransmits > defaults.sctp_max_association_retransmits
970        );
971        assert!(config.sctp_max_heartbeat_failures > defaults.sctp_max_heartbeat_failures);
972        assert!(config.sctp_max_burst > 0); // Explicit burst limit vs. heuristic
973    }
974
975    #[test]
976    fn test_external_port_defaults() {
977        let config = RtcConfiguration::default();
978        assert_eq!(config.external_port, None);
979    }
980
981    #[test]
982    fn test_external_port_builder() {
983        let config = RtcConfigurationBuilder::new().external_port(30000).build();
984        assert_eq!(config.external_port, Some(30000));
985    }
986
987    #[test]
988    fn test_external_port_with_external_ip_builder() {
989        let config = RtcConfigurationBuilder::new()
990            .external_ip("203.0.113.5".to_string())
991            .external_port(30000)
992            .build();
993        assert_eq!(config.external_ip, Some("203.0.113.5".to_string()));
994        assert_eq!(config.external_port, Some(30000));
995    }
996
997    #[test]
998    fn test_upnp_defaults() {
999        let config = RtcConfiguration::default();
1000        assert!(!config.enable_upnp, "UPnP should be disabled by default");
1001        assert_eq!(config.upnp_lease_duration, 3600);
1002    }
1003
1004    #[test]
1005    fn test_upnp_builder_methods() {
1006        let config = RtcConfigurationBuilder::new()
1007            .enable_upnp(false)
1008            .upnp_lease_duration(7200)
1009            .build();
1010        assert!(!config.enable_upnp);
1011        assert_eq!(config.upnp_lease_duration, 7200);
1012    }
1013
1014    #[test]
1015    fn test_upnp_optimized_config() {
1016        let config = RtcConfigurationBuilder::new()
1017            .enable_upnp(true)
1018            .upnp_lease_duration(1800)
1019            .build();
1020
1021        assert!(config.enable_upnp);
1022        assert_eq!(config.upnp_lease_duration, 1800);
1023
1024        // Verify defaults remain for other options
1025        let defaults = RtcConfiguration::default();
1026        assert_eq!(
1027            config.ice_connection_timeout,
1028            defaults.ice_connection_timeout
1029        );
1030    }
1031
1032    #[test]
1033    fn test_ice_udp_mux_defaults() {
1034        let config = RtcConfiguration::default();
1035        assert!(
1036            !config.ice_udp_mux,
1037            "ICE UDP mux should be disabled by default"
1038        );
1039        assert_eq!(config.ice_udp_mux_port, None);
1040    }
1041
1042    #[test]
1043    fn test_ice_udp_mux_builder_methods() {
1044        let config = RtcConfigurationBuilder::new()
1045            .ice_udp_mux(true)
1046            .ice_udp_mux_port(30500)
1047            .build();
1048        assert!(config.ice_udp_mux);
1049        assert_eq!(config.ice_udp_mux_port, Some(30500));
1050    }
1051}