Skip to main content

rustrtc/
config.rs

1use crate::media::depacketizer::{DefaultDepacketizerFactory, DepacketizerFactory};
2use serde::{Deserialize, Serialize};
3use std::fmt::{Debug, Formatter};
4use std::sync::Arc;
5
6/// Describes how credentials are conveyed for a given ICE server.
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
8#[serde(rename_all = "lowercase")]
9pub enum IceCredentialType {
10    #[default]
11    Password,
12    Oauth,
13}
14
15/// Mirrors the W3C `RTCIceServer` dictionary.
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17pub struct IceServer {
18    pub urls: Vec<String>,
19    pub username: Option<String>,
20    pub credential: Option<String>,
21    #[serde(default)]
22    pub credential_type: IceCredentialType,
23}
24
25impl IceServer {
26    pub fn new<T: Into<Vec<String>>>(urls: T) -> Self {
27        Self {
28            urls: urls.into(),
29            username: None,
30            credential: None,
31            credential_type: IceCredentialType::default(),
32        }
33    }
34
35    pub fn with_credential(
36        mut self,
37        username: impl Into<String>,
38        credential: impl Into<String>,
39    ) -> Self {
40        self.username = Some(username.into());
41        self.credential = Some(credential.into());
42        self
43    }
44
45    pub fn credential_type(mut self, kind: IceCredentialType) -> Self {
46        self.credential_type = kind;
47        self
48    }
49}
50
51impl Default for IceServer {
52    fn default() -> Self {
53        Self::new(Vec::new())
54    }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
58pub enum IceTransportPolicy {
59    #[default]
60    All,
61    Relay,
62}
63
64/// Controls ICE TCP candidate support (RFC 6544).
65#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
66pub enum IceTcpPolicy {
67    /// Do not gather or use TCP candidates.
68    #[default]
69    Disabled,
70    /// Gather and use TCP candidates (both active and passive).
71    Enabled,
72    /// Only gather and use passive TCP candidates.
73    PassiveOnly,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
77pub enum BundlePolicy {
78    #[default]
79    Balanced,
80    MaxCompat,
81    MaxBundle,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
85pub enum RtcpMuxPolicy {
86    #[default]
87    Require,
88    Negotiate,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
92pub enum TransportMode {
93    #[default]
94    WebRtc,
95    Srtp,
96    Rtp,
97}
98
99/// Strategy for dropping packets when buffer is full.
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
101pub enum BufferDropStrategy {
102    #[default]
103    DropNew,
104    DropOldest,
105}
106
107/// Tracks user-supplied certificate material.
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
109pub struct CertificateConfig {
110    pub pem_chain: Vec<String>,
111    pub private_key_pem: Option<String>,
112}
113
114/// Configuration for audio/video codecs and parameters.
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
116pub struct AudioCapability {
117    pub payload_type: u8,
118    pub codec_name: String,
119    pub clock_rate: u32,
120    pub channels: u8,
121    pub fmtp: Option<String>,
122    pub rtcp_fbs: Vec<String>,
123}
124
125impl Default for AudioCapability {
126    fn default() -> Self {
127        Self {
128            payload_type: 111,
129            codec_name: "opus".to_string(),
130            clock_rate: 48000,
131            channels: 2,
132            fmtp: Some("minptime=10;useinbandfec=1;stereo=1".to_string()),
133            rtcp_fbs: vec![],
134        }
135    }
136}
137
138impl AudioCapability {
139    pub fn opus() -> Self {
140        Self::default()
141    }
142
143    pub fn pcmu() -> Self {
144        Self {
145            payload_type: 0,
146            codec_name: "PCMU".to_string(),
147            clock_rate: 8000,
148            channels: 1,
149            fmtp: None,
150            rtcp_fbs: vec![],
151        }
152    }
153
154    pub fn pcma() -> Self {
155        Self {
156            payload_type: 8,
157            codec_name: "PCMA".to_string(),
158            clock_rate: 8000,
159            channels: 1,
160            fmtp: None,
161            rtcp_fbs: vec![],
162        }
163    }
164
165    pub fn g722() -> Self {
166        Self {
167            payload_type: 9,
168            codec_name: "G722".to_string(),
169            clock_rate: 8000,
170            channels: 1,
171            fmtp: None,
172            rtcp_fbs: vec![],
173        }
174    }
175
176    pub fn g729() -> Self {
177        Self {
178            payload_type: 18,
179            codec_name: "G729".to_string(),
180            clock_rate: 8000,
181            channels: 1,
182            fmtp: None,
183            rtcp_fbs: vec![],
184        }
185    }
186
187    pub fn telephone_event() -> Self {
188        Self {
189            payload_type: 101,
190            codec_name: "telephone-event".to_string(),
191            clock_rate: 8000,
192            channels: 1,
193            fmtp: Some("0-16".to_string()),
194            rtcp_fbs: vec![],
195        }
196    }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
200pub struct VideoCapability {
201    pub payload_type: u8,
202    pub codec_name: String,
203    pub clock_rate: u32,
204    pub fmtp: Option<String>,
205    pub rtcp_fbs: Vec<String>,
206}
207
208impl Default for VideoCapability {
209    fn default() -> Self {
210        Self {
211            payload_type: 96,
212            codec_name: "VP8".to_string(),
213            clock_rate: 90000,
214            fmtp: None,
215            rtcp_fbs: vec![
216                "nack".to_string(),
217                "nack pli".to_string(),
218                "ccm fir".to_string(),
219                "goog-remb".to_string(),
220                "transport-cc".to_string(),
221            ],
222        }
223    }
224}
225
226impl VideoCapability {
227    pub fn h264() -> Self {
228        Self {
229            payload_type: 96,
230            codec_name: "H264".to_string(),
231            clock_rate: 90000,
232            fmtp: Some("packetization-mode=1;profile-level-id=42e01f".to_string()),
233            rtcp_fbs: vec!["nack pli".to_string(), "ccm fir".to_string()],
234        }
235    }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
239pub struct ApplicationCapability {
240    pub sctp_port: u16,
241}
242
243impl Default for ApplicationCapability {
244    fn default() -> Self {
245        Self { sctp_port: 5000 }
246    }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
250pub enum T38FaxRateManagement {
251    #[serde(rename = "transferredTCF")]
252    TransferredTCF,
253    #[serde(rename = "localTCF")]
254    LocalTCF,
255}
256
257impl Default for T38FaxRateManagement {
258    fn default() -> Self {
259        Self::TransferredTCF
260    }
261}
262
263impl std::fmt::Display for T38FaxRateManagement {
264    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265        match self {
266            Self::TransferredTCF => write!(f, "transferredTCF"),
267            Self::LocalTCF => write!(f, "localTCF"),
268        }
269    }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
273pub enum T38UdpEC {
274    #[serde(rename = "t38UDPRedundancy")]
275    T38UDPRedundancy,
276    #[serde(rename = "t38UDPFEC")]
277    T38UDPFEC,
278}
279
280impl Default for T38UdpEC {
281    fn default() -> Self {
282        Self::T38UDPRedundancy
283    }
284}
285
286impl std::fmt::Display for T38UdpEC {
287    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        match self {
289            Self::T38UDPRedundancy => write!(f, "t38UDPRedundancy"),
290            Self::T38UDPFEC => write!(f, "t38UDPFEC"),
291        }
292    }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
296pub struct T38Capability {
297    pub payload_type: u8,
298    /// T.38 version (0-3)
299    pub version: u8,
300    /// Max bit rate in bps (e.g. 14400, 9600, 4800, 2400)
301    pub max_bitrate: u32,
302    /// Rate management method
303    pub rate_management: T38FaxRateManagement,
304    /// Max buffer size in bytes
305    pub max_buffer: u16,
306    /// Max datagram size in bytes
307    pub max_datagram: u16,
308    /// UDP error correction method
309    pub udp_ec: T38UdpEC,
310    pub fmtp: Option<String>,
311}
312
313impl Default for T38Capability {
314    fn default() -> Self {
315        Self {
316            payload_type: 98,
317            version: 0,
318            max_bitrate: 14400,
319            rate_management: T38FaxRateManagement::default(),
320            max_buffer: 1024,
321            max_datagram: 238,
322            udp_ec: T38UdpEC::default(),
323            fmtp: None,
324        }
325    }
326}
327
328impl T38Capability {
329    pub fn default_t38() -> Self {
330        Self::default()
331    }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
335pub struct MediaCapabilities {
336    pub audio: Vec<AudioCapability>,
337    pub video: Vec<VideoCapability>,
338    pub application: Option<ApplicationCapability>,
339    pub image: Vec<T38Capability>,
340}
341
342impl Default for MediaCapabilities {
343    fn default() -> Self {
344        Self {
345            audio: vec![AudioCapability::opus(), AudioCapability::pcmu()],
346            video: vec![VideoCapability::default()],
347            application: Some(ApplicationCapability::default()),
348            image: vec![],
349        }
350    }
351}
352
353#[derive(Clone)]
354pub struct DepacketizerStrategy {
355    pub factory: Arc<dyn DepacketizerFactory>,
356}
357
358impl Default for DepacketizerStrategy {
359    fn default() -> Self {
360        Self {
361            factory: Arc::new(DefaultDepacketizerFactory),
362        }
363    }
364}
365
366impl Debug for DepacketizerStrategy {
367    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
368        self.factory.fmt(f)
369    }
370}
371
372impl PartialEq for DepacketizerStrategy {
373    fn eq(&self, other: &Self) -> bool {
374        Arc::ptr_eq(&self.factory, &other.factory)
375    }
376}
377
378impl Eq for DepacketizerStrategy {}
379
380fn default_rtp_buffer_capacity() -> usize {
381    100
382}
383
384fn default_buffer_stats_log_interval() -> std::time::Duration {
385    std::time::Duration::from_secs(10)
386}
387
388/// Controls SDP generation compatibility for interoperability with legacy SIP endpoints.
389#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
390#[serde(rename_all = "snake_case")]
391pub enum SdpCompatibilityMode {
392    /// Standard WebRTC / RFC-compliant SDP output (default).
393    #[default]
394    Standard,
395    /// Compatibility mode for legacy SIP endpoints (e.g. Linphone):
396    /// omits `a=mid` unless BUNDLE is active, omits `a=rtcp-mux`.
397    LegacySip,
398}
399
400fn default_enable_upnp() -> bool {
401    false
402}
403
404fn default_upnp_lease_duration() -> u32 {
405    3600
406}
407
408/// Primary configuration for a `PeerConnection`.
409#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
410pub struct RtcConfiguration {
411    pub ice_servers: Vec<IceServer>,
412    pub ice_transport_policy: IceTransportPolicy,
413    pub bundle_policy: BundlePolicy,
414    pub rtcp_mux_policy: RtcpMuxPolicy,
415    pub certificates: Vec<CertificateConfig>,
416    pub transport_mode: TransportMode,
417    pub nack_buffer_size: usize,
418    pub media_capabilities: Option<MediaCapabilities>,
419    /// Override the advertised IP address in SDP (for NAT traversal).
420    /// When set, the `c=`, `o=`, and candidate addresses in the SDP will
421    /// use this IP instead of the local bind IP. The local bind address is
422    /// stored in `related_address` on the candidate.
423    pub external_ip: Option<String>,
424    /// Override the advertised port in SDP `m=` line and candidates
425    /// (for NAT port forwarding).
426    ///
427    /// When set, the SDP will advertise this port instead of the local
428    /// bind port. This is useful when you have configured NAT port
429    /// forwarding (e.g. external port 30000 → local port 20000) and need
430    /// the remote peer to send RTP to the external port.
431    ///
432    /// Works independently or combined with `external_ip`.
433    /// Only applies in RTP/SRTP direct mode (`TransportMode::Rtp` /
434    /// `TransportMode::Srtp`). Not used in WebRTC mode.
435    pub external_port: Option<u16>,
436    pub bind_ip: Option<String>,
437    pub disable_ipv6: bool,
438    pub ssrc_start: u32,
439    pub stun_timeout: std::time::Duration,
440    /// Timeout for the ICE nomination binding check (USE-CANDIDATE).
441    /// This should be larger than `stun_timeout` to allow more retransmissions
442    /// and reduce the probability of nomination failures under packet loss.
443    pub nomination_timeout: std::time::Duration,
444    pub ice_connection_timeout: std::time::Duration,
445    pub sctp_rto_initial: std::time::Duration,
446    pub sctp_rto_min: std::time::Duration,
447    pub sctp_rto_max: std::time::Duration,
448    pub sctp_max_association_retransmits: u32,
449    pub sctp_receive_window: usize,
450    pub sctp_heartbeat_interval: std::time::Duration,
451    pub sctp_max_heartbeat_failures: u32,
452    pub sctp_max_tsn_retransmits: u32,
453    pub sctp_max_burst: usize,
454    pub sctp_max_cwnd: usize,
455    pub dtls_buffer_size: usize,
456    pub rtp_start_port: Option<u16>,
457    pub rtp_end_port: Option<u16>,
458    pub ice_gather_udp_hosts: bool,
459    pub tcp_port_range_start: Option<u16>,
460    pub tcp_port_range_end: Option<u16>,
461    pub enable_latching: bool,
462    pub probation_max_packets: Option<u8>,
463    pub enable_ice_lite: bool,
464    /// When true, demote host candidates with private (RFC 1918) local IPs
465    /// below server-reflexive candidates in the connectivity check ordering.
466    /// This avoids DTLS handshake failures behind NATs where a host candidate
467    /// can pass a single STUN binding check but cannot sustain bidirectional
468    /// DTLS traffic.  Same-LAN pairs (both sides private) are not affected.
469    /// Default: false (standard RFC 5245 behavior).
470    #[serde(default)]
471    pub prefer_srflx_over_natted_host: bool,
472    /// Enable UPnP IGD for automatic port mapping
473    #[serde(default = "default_enable_upnp")]
474    pub enable_upnp: bool,
475    /// UPnP port mapping lease duration in seconds
476    #[serde(default = "default_upnp_lease_duration")]
477    pub upnp_lease_duration: u32,
478    #[serde(skip, default)]
479    pub depacketizer_strategy: DepacketizerStrategy,
480    #[serde(default = "default_rtp_buffer_capacity")]
481    pub rtp_buffer_capacity: usize,
482    #[serde(default)]
483    pub buffer_drop_strategy: BufferDropStrategy,
484    #[serde(default = "default_buffer_stats_log_interval")]
485    pub buffer_stats_log_interval: std::time::Duration,
486    /// Controls ICE TCP candidate support (RFC 6544).
487    /// Default: Disabled — only UDP candidates are gathered and used.
488    #[serde(default)]
489    pub ice_tcp_policy: IceTcpPolicy,
490    /// SDP generation compatibility mode.
491    #[serde(default)]
492    pub sdp_compatibility: SdpCompatibilityMode,
493    #[serde(skip, default)]
494    pub label: Option<String>,
495    #[serde(skip, default)]
496    pub cname: Option<String>,
497}
498
499impl Default for RtcConfiguration {
500    fn default() -> Self {
501        Self {
502            ice_servers: Vec::new(),
503            ice_transport_policy: IceTransportPolicy::default(),
504            bundle_policy: BundlePolicy::default(),
505            rtcp_mux_policy: RtcpMuxPolicy::default(),
506            certificates: Vec::new(),
507            transport_mode: TransportMode::default(),
508            nack_buffer_size: 200,
509            media_capabilities: None,
510            external_ip: None,
511            external_port: None,
512            bind_ip: None,
513            disable_ipv6: false,
514            ssrc_start: 10000,
515            stun_timeout: std::time::Duration::from_secs(5),
516            nomination_timeout: std::time::Duration::from_secs(10),
517            ice_connection_timeout: std::time::Duration::from_secs(30),
518            sctp_rto_initial: std::time::Duration::from_secs(3),
519            sctp_rto_min: std::time::Duration::from_secs(1),
520            sctp_rto_max: std::time::Duration::from_secs(60),
521            sctp_max_association_retransmits: 20,
522            sctp_receive_window: 128 * 1024, // 128KB - reduced for lower memory footprint
523            sctp_heartbeat_interval: std::time::Duration::from_secs(15),
524            sctp_max_heartbeat_failures: 4,
525            sctp_max_tsn_retransmits: 8,
526            sctp_max_burst: 0,         // 0 = use default heuristic
527            sctp_max_cwnd: 256 * 1024, // 256 KB
528            dtls_buffer_size: 2048,
529            rtp_start_port: None,
530            rtp_end_port: None,
531            ice_gather_udp_hosts: true,
532            tcp_port_range_start: None,
533            tcp_port_range_end: None,
534            enable_latching: false,
535            probation_max_packets: None,
536            enable_ice_lite: false,
537            prefer_srflx_over_natted_host: false,
538            enable_upnp: default_enable_upnp(),
539            upnp_lease_duration: default_upnp_lease_duration(),
540            depacketizer_strategy: DepacketizerStrategy::default(),
541            rtp_buffer_capacity: default_rtp_buffer_capacity(),
542            buffer_drop_strategy: BufferDropStrategy::default(),
543            buffer_stats_log_interval: default_buffer_stats_log_interval(),
544            ice_tcp_policy: IceTcpPolicy::default(),
545            sdp_compatibility: SdpCompatibilityMode::default(),
546            label: None,
547            cname: None,
548        }
549    }
550}
551
552pub struct RtcConfigurationBuilder {
553    inner: RtcConfiguration,
554}
555
556impl Default for RtcConfigurationBuilder {
557    fn default() -> Self {
558        Self::new()
559    }
560}
561
562impl RtcConfigurationBuilder {
563    pub fn new() -> Self {
564        Self {
565            inner: RtcConfiguration::default(),
566        }
567    }
568
569    pub fn enable_latching(mut self, enable: bool) -> Self {
570        self.inner.enable_latching = enable;
571        self
572    }
573
574    pub fn probation_max_packets(mut self, max: Option<u8>) -> Self {
575        self.inner.probation_max_packets = max;
576        self
577    }
578
579    pub fn enable_ice_lite(mut self, enable: bool) -> Self {
580        self.inner.enable_ice_lite = enable;
581        self
582    }
583
584    pub fn prefer_srflx_over_natted_host(mut self, enable: bool) -> Self {
585        self.inner.prefer_srflx_over_natted_host = enable;
586        self
587    }
588
589    pub fn enable_upnp(mut self, enable: bool) -> Self {
590        self.inner.enable_upnp = enable;
591        self
592    }
593
594    pub fn upnp_lease_duration(mut self, duration_secs: u32) -> Self {
595        self.inner.upnp_lease_duration = duration_secs;
596        self
597    }
598
599    pub fn ice_server(mut self, server: IceServer) -> Self {
600        self.inner.ice_servers.push(server);
601        self
602    }
603
604    pub fn ice_transport_policy(mut self, policy: IceTransportPolicy) -> Self {
605        self.inner.ice_transport_policy = policy;
606        self
607    }
608
609    pub fn bundle_policy(mut self, policy: BundlePolicy) -> Self {
610        self.inner.bundle_policy = policy;
611        self
612    }
613
614    pub fn rtcp_mux_policy(mut self, policy: RtcpMuxPolicy) -> Self {
615        self.inner.rtcp_mux_policy = policy;
616        self
617    }
618
619    pub fn certificate(mut self, cert: CertificateConfig) -> Self {
620        self.inner.certificates.push(cert);
621        self
622    }
623
624    pub fn transport_mode(mut self, mode: TransportMode) -> Self {
625        self.inner.transport_mode = mode;
626        self
627    }
628
629    pub fn media_capabilities(mut self, capabilities: MediaCapabilities) -> Self {
630        self.inner.media_capabilities = Some(capabilities);
631        self
632    }
633
634    pub fn external_ip(mut self, ip: String) -> Self {
635        self.inner.external_ip = Some(ip);
636        self
637    }
638
639    pub fn external_port(mut self, port: u16) -> Self {
640        self.inner.external_port = Some(port);
641        self
642    }
643
644    pub fn bind_ip(mut self, ip: String) -> Self {
645        self.inner.bind_ip = Some(ip);
646        self
647    }
648
649    pub fn disable_ipv6(mut self, disable: bool) -> Self {
650        self.inner.disable_ipv6 = disable;
651        self
652    }
653
654    pub fn ssrc_start(mut self, start: u32) -> Self {
655        self.inner.ssrc_start = start;
656        self
657    }
658
659    pub fn stun_timeout(mut self, timeout: std::time::Duration) -> Self {
660        self.inner.stun_timeout = timeout;
661        self
662    }
663
664    pub fn nomination_timeout(mut self, timeout: std::time::Duration) -> Self {
665        self.inner.nomination_timeout = timeout;
666        self
667    }
668
669    pub fn rtp_port_range(mut self, start: u16, end: u16) -> Self {
670        self.inner.rtp_start_port = Some(start);
671        self.inner.rtp_end_port = Some(end);
672        self
673    }
674
675    pub fn ice_gather_udp_hosts(mut self, enable: bool) -> Self {
676        self.inner.ice_gather_udp_hosts = enable;
677        self
678    }
679
680    pub fn tcp_port_range(mut self, start: u16, end: u16) -> Self {
681        self.inner.tcp_port_range_start = Some(start);
682        self.inner.tcp_port_range_end = Some(end);
683        self
684    }
685
686    pub fn dtls_buffer_size(mut self, size: usize) -> Self {
687        self.inner.dtls_buffer_size = size;
688        self
689    }
690
691    pub fn sctp_rto_initial(mut self, duration: std::time::Duration) -> Self {
692        self.inner.sctp_rto_initial = duration;
693        self
694    }
695
696    pub fn sctp_rto_min(mut self, duration: std::time::Duration) -> Self {
697        self.inner.sctp_rto_min = duration;
698        self
699    }
700
701    pub fn sctp_rto_max(mut self, duration: std::time::Duration) -> Self {
702        self.inner.sctp_rto_max = duration;
703        self
704    }
705
706    pub fn sctp_max_association_retransmits(mut self, count: u32) -> Self {
707        self.inner.sctp_max_association_retransmits = count;
708        self
709    }
710
711    pub fn sctp_receive_window(mut self, size: usize) -> Self {
712        self.inner.sctp_receive_window = size;
713        self
714    }
715
716    pub fn sctp_heartbeat_interval(mut self, duration: std::time::Duration) -> Self {
717        self.inner.sctp_heartbeat_interval = duration;
718        self
719    }
720
721    pub fn sctp_max_heartbeat_failures(mut self, count: u32) -> Self {
722        self.inner.sctp_max_heartbeat_failures = count;
723        self
724    }
725
726    /// Set the maximum burst size for SCTP in number of MTU-sized packets.
727    /// 0 means use the default heuristic (16 packets normal, 4 in recovery).
728    /// For rate-limited TURN relays, a value of 2-4 can reduce burst-induced
729    /// packet loss.
730    pub fn sctp_max_burst(mut self, packets: usize) -> Self {
731        self.inner.sctp_max_burst = packets;
732        self
733    }
734
735    /// Set the maximum congestion window size in bytes.
736    /// Default is 256 KB. For high-latency TURN relays, consider 512KB-1MB.
737    pub fn sctp_max_cwnd(mut self, size: usize) -> Self {
738        self.inner.sctp_max_cwnd = size;
739        self
740    }
741
742    pub fn ice_connection_timeout(mut self, timeout: std::time::Duration) -> Self {
743        self.inner.ice_connection_timeout = timeout;
744        self
745    }
746
747    pub fn rtp_buffer_capacity(mut self, capacity: usize) -> Self {
748        self.inner.rtp_buffer_capacity = capacity;
749        self
750    }
751
752    pub fn buffer_drop_strategy(mut self, strategy: BufferDropStrategy) -> Self {
753        self.inner.buffer_drop_strategy = strategy;
754        self
755    }
756
757    pub fn buffer_stats_log_interval(mut self, interval: std::time::Duration) -> Self {
758        self.inner.buffer_stats_log_interval = interval;
759        self
760    }
761
762    pub fn ice_tcp_policy(mut self, policy: IceTcpPolicy) -> Self {
763        self.inner.ice_tcp_policy = policy;
764        self
765    }
766
767    pub fn sdp_compatibility(mut self, mode: SdpCompatibilityMode) -> Self {
768        self.inner.sdp_compatibility = mode;
769        self
770    }
771
772    pub fn cname(mut self, cname: String) -> Self {
773        self.inner.cname = Some(cname);
774        self
775    }
776
777    pub fn build(self) -> RtcConfiguration {
778        self.inner
779    }
780}
781
782impl From<RtcConfigurationBuilder> for RtcConfiguration {
783    fn from(builder: RtcConfigurationBuilder) -> Self {
784        builder.build()
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791    use std::time::Duration;
792
793    #[test]
794    fn test_rtc_configuration_defaults() {
795        let config = RtcConfiguration::default();
796        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
797        assert_eq!(config.sctp_rto_initial, Duration::from_secs(3));
798        assert_eq!(config.sctp_rto_min, Duration::from_secs(1));
799        assert_eq!(config.sctp_rto_max, Duration::from_secs(60));
800        assert_eq!(config.sctp_max_association_retransmits, 20);
801        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(15));
802        assert_eq!(config.sctp_max_heartbeat_failures, 4);
803        assert_eq!(config.sctp_max_burst, 0);
804        assert_eq!(config.sctp_max_cwnd, 256 * 1024);
805        assert_eq!(config.rtp_buffer_capacity, 100);
806        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropNew);
807        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(10));
808    }
809
810    #[test]
811    fn test_rtc_configuration_builder() {
812        let config = RtcConfigurationBuilder::new()
813            .stun_timeout(Duration::from_secs(10))
814            .build();
815        assert_eq!(config.stun_timeout, Duration::from_secs(10));
816        // Verify other defaults are still there
817        assert_eq!(config.ice_connection_timeout, Duration::from_secs(30));
818    }
819
820    #[test]
821    fn test_buffer_config_builder() {
822        let config = RtcConfigurationBuilder::new()
823            .rtp_buffer_capacity(200)
824            .buffer_drop_strategy(BufferDropStrategy::DropOldest)
825            .buffer_stats_log_interval(Duration::from_secs(5))
826            .build();
827        assert_eq!(config.rtp_buffer_capacity, 200);
828        assert_eq!(config.buffer_drop_strategy, BufferDropStrategy::DropOldest);
829        assert_eq!(config.buffer_stats_log_interval, Duration::from_secs(5));
830    }
831
832    #[test]
833    fn test_sctp_builder_methods() {
834        let config = RtcConfigurationBuilder::new()
835            .sctp_rto_initial(Duration::from_millis(500))
836            .sctp_rto_min(Duration::from_millis(200))
837            .sctp_rto_max(Duration::from_secs(10))
838            .sctp_max_association_retransmits(30)
839            .sctp_receive_window(512 * 1024)
840            .sctp_heartbeat_interval(Duration::from_secs(10))
841            .sctp_max_heartbeat_failures(8)
842            .sctp_max_burst(4)
843            .sctp_max_cwnd(512 * 1024)
844            .ice_connection_timeout(Duration::from_secs(60))
845            .build();
846
847        assert_eq!(config.sctp_rto_initial, Duration::from_millis(500));
848        assert_eq!(config.sctp_rto_min, Duration::from_millis(200));
849        assert_eq!(config.sctp_rto_max, Duration::from_secs(10));
850        assert_eq!(config.sctp_max_association_retransmits, 30);
851        assert_eq!(config.sctp_receive_window, 512 * 1024);
852        assert_eq!(config.sctp_heartbeat_interval, Duration::from_secs(10));
853        assert_eq!(config.sctp_max_heartbeat_failures, 8);
854        assert_eq!(config.sctp_max_burst, 4);
855        assert_eq!(config.sctp_max_cwnd, 512 * 1024);
856        assert_eq!(config.ice_connection_timeout, Duration::from_secs(60));
857    }
858
859    #[test]
860    fn test_turn_optimized_config() {
861        // Verify a TURN-optimized configuration can be expressed cleanly
862        let config = RtcConfigurationBuilder::new()
863            .sctp_rto_initial(Duration::from_millis(500))
864            .sctp_rto_min(Duration::from_millis(200))
865            .sctp_rto_max(Duration::from_secs(10))
866            .sctp_max_association_retransmits(30)
867            .sctp_max_heartbeat_failures(8)
868            .sctp_max_burst(4)
869            .stun_timeout(Duration::from_secs(10))
870            .nomination_timeout(Duration::from_secs(20))
871            .build();
872
873        // Verify the TURN-optimized values are more aggressive than defaults
874        let defaults = RtcConfiguration::default();
875        assert!(config.sctp_rto_initial < defaults.sctp_rto_initial);
876        assert!(config.sctp_rto_min < defaults.sctp_rto_min);
877        assert!(config.sctp_rto_max < defaults.sctp_rto_max);
878        assert!(
879            config.sctp_max_association_retransmits > defaults.sctp_max_association_retransmits
880        );
881        assert!(config.sctp_max_heartbeat_failures > defaults.sctp_max_heartbeat_failures);
882        assert!(config.sctp_max_burst > 0); // Explicit burst limit vs. heuristic
883    }
884
885    #[test]
886    fn test_external_port_defaults() {
887        let config = RtcConfiguration::default();
888        assert_eq!(config.external_port, None);
889    }
890
891    #[test]
892    fn test_external_port_builder() {
893        let config = RtcConfigurationBuilder::new()
894            .external_port(30000)
895            .build();
896        assert_eq!(config.external_port, Some(30000));
897    }
898
899    #[test]
900    fn test_external_port_with_external_ip_builder() {
901        let config = RtcConfigurationBuilder::new()
902            .external_ip("203.0.113.5".to_string())
903            .external_port(30000)
904            .build();
905        assert_eq!(config.external_ip, Some("203.0.113.5".to_string()));
906        assert_eq!(config.external_port, Some(30000));
907    }
908
909    #[test]
910    fn test_upnp_defaults() {
911        let config = RtcConfiguration::default();
912        assert!(!config.enable_upnp, "UPnP should be disabled by default");
913        assert_eq!(config.upnp_lease_duration, 3600);
914    }
915
916    #[test]
917    fn test_upnp_builder_methods() {
918        let config = RtcConfigurationBuilder::new()
919            .enable_upnp(false)
920            .upnp_lease_duration(7200)
921            .build();
922        assert!(!config.enable_upnp);
923        assert_eq!(config.upnp_lease_duration, 7200);
924    }
925
926    #[test]
927    fn test_upnp_optimized_config() {
928        let config = RtcConfigurationBuilder::new()
929            .enable_upnp(true)
930            .upnp_lease_duration(1800)
931            .build();
932
933        assert!(config.enable_upnp);
934        assert_eq!(config.upnp_lease_duration, 1800);
935
936        // Verify defaults remain for other options
937        let defaults = RtcConfiguration::default();
938        assert_eq!(
939            config.ice_connection_timeout,
940            defaults.ice_connection_timeout
941        );
942    }
943}