Skip to main content

rtmp_rs/server/
config.rs

1//! Server configuration
2
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use crate::media::fourcc::{AudioFourCc, VideoFourCc};
7use crate::protocol::constants::*;
8use crate::protocol::enhanced::{CapsEx, EnhancedRtmpMode, FourCcCapability};
9
10/// Server configuration options
11#[derive(Debug, Clone)]
12pub struct ServerConfig {
13    /// Address to bind to
14    pub bind_addr: SocketAddr,
15
16    /// Maximum concurrent connections (0 = unlimited)
17    pub max_connections: usize,
18
19    /// Chunk size to negotiate with clients
20    pub chunk_size: u32,
21
22    /// Window acknowledgement size
23    pub window_ack_size: u32,
24
25    /// Peer bandwidth limit
26    pub peer_bandwidth: u32,
27
28    /// Connection timeout (handshake must complete within this time)
29    pub connection_timeout: Duration,
30
31    /// Idle timeout (disconnect if no data received)
32    pub idle_timeout: Duration,
33
34    /// Enable TCP_NODELAY (disable Nagle's algorithm)
35    pub tcp_nodelay: bool,
36
37    /// TCP receive buffer size (0 = OS default)
38    pub tcp_recv_buffer: usize,
39
40    /// TCP send buffer size (0 = OS default)
41    pub tcp_send_buffer: usize,
42
43    /// Application-level read buffer size
44    pub read_buffer_size: usize,
45
46    /// Application-level write buffer size
47    pub write_buffer_size: usize,
48
49    /// Enable GOP buffering for late-joiner support
50    pub gop_buffer_enabled: bool,
51
52    /// Maximum GOP buffer size in bytes
53    pub gop_buffer_max_size: usize,
54
55    /// Stats update interval
56    pub stats_interval: Duration,
57
58    /// Enhanced RTMP mode (Auto, LegacyOnly, or EnhancedOnly)
59    pub enhanced_rtmp: EnhancedRtmpMode,
60
61    /// Enhanced RTMP server capabilities to advertise
62    pub enhanced_capabilities: EnhancedServerCapabilities,
63}
64
65/// Server-side Enhanced RTMP capabilities.
66///
67/// Configure which E-RTMP features and codecs the server supports.
68#[derive(Debug, Clone)]
69pub struct EnhancedServerCapabilities {
70    /// Support for NetConnection.Connect.ReconnectRequest
71    pub reconnect: bool,
72
73    /// Support for multitrack audio/video streams
74    pub multitrack: bool,
75
76    /// Support for ModEx signal parsing (nanosecond timestamps, etc.)
77    pub modex: bool,
78
79    /// Video codecs supported with their capabilities
80    pub video_codecs: Vec<(VideoFourCc, FourCcCapability)>,
81
82    /// Audio codecs supported with their capabilities
83    pub audio_codecs: Vec<(AudioFourCc, FourCcCapability)>,
84}
85
86impl Default for EnhancedServerCapabilities {
87    fn default() -> Self {
88        Self {
89            reconnect: false,
90            multitrack: false,
91            modex: true, // Parse ModEx but don't require it
92            video_codecs: vec![
93                (VideoFourCc::Avc, FourCcCapability::forward()),
94                (VideoFourCc::Hevc, FourCcCapability::forward()),
95                (VideoFourCc::Av1, FourCcCapability::forward()),
96                (VideoFourCc::Vp9, FourCcCapability::forward()),
97            ],
98            audio_codecs: vec![
99                (AudioFourCc::Aac, FourCcCapability::forward()),
100                (AudioFourCc::Opus, FourCcCapability::forward()),
101            ],
102        }
103    }
104}
105
106impl EnhancedServerCapabilities {
107    /// Create capabilities with no codec support (minimal E-RTMP).
108    pub fn minimal() -> Self {
109        Self {
110            reconnect: false,
111            multitrack: false,
112            modex: true,
113            video_codecs: vec![],
114            audio_codecs: vec![],
115        }
116    }
117
118    /// Add a video codec with specified capability.
119    pub fn with_video_codec(mut self, codec: VideoFourCc, cap: FourCcCapability) -> Self {
120        self.video_codecs.push((codec, cap));
121        self
122    }
123
124    /// Add an audio codec with specified capability.
125    pub fn with_audio_codec(mut self, codec: AudioFourCc, cap: FourCcCapability) -> Self {
126        self.audio_codecs.push((codec, cap));
127        self
128    }
129
130    /// Enable reconnect support.
131    pub fn with_reconnect(mut self) -> Self {
132        self.reconnect = true;
133        self
134    }
135
136    /// Enable multitrack support.
137    pub fn with_multitrack(mut self) -> Self {
138        self.multitrack = true;
139        self
140    }
141
142    /// Convert to CapsEx bitmask for protocol encoding.
143    pub fn to_caps_ex(&self) -> CapsEx {
144        let mut caps = CapsEx::empty();
145        if self.reconnect {
146            caps.insert(CapsEx::RECONNECT);
147        }
148        if self.multitrack {
149            caps.insert(CapsEx::MULTITRACK);
150        }
151        if self.modex {
152            caps.insert(CapsEx::MODEX);
153        }
154        caps
155    }
156
157    /// Convert to EnhancedCapabilities for negotiation.
158    pub fn to_enhanced_capabilities(&self) -> crate::protocol::enhanced::EnhancedCapabilities {
159        use crate::protocol::enhanced::EnhancedCapabilities;
160
161        let mut caps = EnhancedCapabilities {
162            enabled: true,
163            caps_ex: self.to_caps_ex(),
164            video_codecs: std::collections::HashMap::new(),
165            audio_codecs: std::collections::HashMap::new(),
166            video_function: crate::protocol::enhanced::VideoFunctionFlags::empty(),
167        };
168
169        for (codec, capability) in &self.video_codecs {
170            caps.video_codecs.insert(*codec, *capability);
171        }
172
173        for (codec, capability) in &self.audio_codecs {
174            caps.audio_codecs.insert(*codec, *capability);
175        }
176
177        caps
178    }
179}
180
181impl Default for ServerConfig {
182    fn default() -> Self {
183        Self {
184            bind_addr: "0.0.0.0:1935".parse().unwrap(),
185            max_connections: 0, // Unlimited
186            chunk_size: RECOMMENDED_CHUNK_SIZE,
187            window_ack_size: DEFAULT_WINDOW_ACK_SIZE,
188            peer_bandwidth: DEFAULT_PEER_BANDWIDTH,
189            connection_timeout: Duration::from_secs(10),
190            idle_timeout: Duration::from_secs(60),
191            tcp_nodelay: true, // Important for low latency
192            tcp_recv_buffer: 0,
193            tcp_send_buffer: 0,
194            read_buffer_size: 64 * 1024, // 64KB
195            write_buffer_size: 64 * 1024,
196            gop_buffer_enabled: true,
197            gop_buffer_max_size: 4 * 1024 * 1024, // 4MB
198            stats_interval: Duration::from_secs(5),
199            enhanced_rtmp: EnhancedRtmpMode::Auto,
200            enhanced_capabilities: EnhancedServerCapabilities::default(),
201        }
202    }
203}
204
205impl ServerConfig {
206    /// Create a new config with custom bind address
207    pub fn with_addr(addr: SocketAddr) -> Self {
208        Self {
209            bind_addr: addr,
210            ..Default::default()
211        }
212    }
213
214    /// Set the bind address
215    pub fn bind(mut self, addr: SocketAddr) -> Self {
216        self.bind_addr = addr;
217        self
218    }
219
220    /// Set maximum connections
221    pub fn max_connections(mut self, max: usize) -> Self {
222        self.max_connections = max;
223        self
224    }
225
226    /// Set chunk size
227    pub fn chunk_size(mut self, size: u32) -> Self {
228        self.chunk_size = size.min(MAX_CHUNK_SIZE);
229        self
230    }
231
232    /// Disable GOP buffering
233    pub fn disable_gop_buffer(mut self) -> Self {
234        self.gop_buffer_enabled = false;
235        self
236    }
237
238    /// Set connection timeout
239    pub fn connection_timeout(mut self, timeout: Duration) -> Self {
240        self.connection_timeout = timeout;
241        self
242    }
243
244    /// Set idle timeout
245    pub fn idle_timeout(mut self, timeout: Duration) -> Self {
246        self.idle_timeout = timeout;
247        self
248    }
249
250    /// Set Enhanced RTMP mode.
251    ///
252    /// - `Auto`: Negotiate E-RTMP if client supports it (default)
253    /// - `LegacyOnly`: Use legacy RTMP only
254    /// - `EnhancedOnly`: Require E-RTMP, reject legacy clients
255    pub fn enhanced_rtmp(mut self, mode: EnhancedRtmpMode) -> Self {
256        self.enhanced_rtmp = mode;
257        self
258    }
259
260    /// Set Enhanced RTMP server capabilities.
261    pub fn enhanced_capabilities(mut self, caps: EnhancedServerCapabilities) -> Self {
262        self.enhanced_capabilities = caps;
263        self
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    #[test]
272    fn test_default_config() {
273        let config = ServerConfig::default();
274
275        assert_eq!(config.bind_addr.port(), 1935);
276        assert_eq!(config.max_connections, 0);
277        assert_eq!(config.chunk_size, RECOMMENDED_CHUNK_SIZE);
278        assert_eq!(config.window_ack_size, DEFAULT_WINDOW_ACK_SIZE);
279        assert_eq!(config.peer_bandwidth, DEFAULT_PEER_BANDWIDTH);
280        assert!(config.tcp_nodelay);
281        assert!(config.gop_buffer_enabled);
282        assert_eq!(config.enhanced_rtmp, EnhancedRtmpMode::Auto);
283    }
284
285    #[test]
286    fn test_with_addr() {
287        let addr: SocketAddr = "127.0.0.1:1936".parse().unwrap();
288        let config = ServerConfig::with_addr(addr);
289
290        assert_eq!(config.bind_addr.port(), 1936);
291    }
292
293    #[test]
294    fn test_builder_bind() {
295        let addr: SocketAddr = "0.0.0.0:8080".parse().unwrap();
296        let config = ServerConfig::default().bind(addr);
297
298        assert_eq!(config.bind_addr, addr);
299    }
300
301    #[test]
302    fn test_builder_max_connections() {
303        let config = ServerConfig::default().max_connections(100);
304
305        assert_eq!(config.max_connections, 100);
306    }
307
308    #[test]
309    fn test_builder_chunk_size() {
310        let config = ServerConfig::default().chunk_size(8192);
311
312        assert_eq!(config.chunk_size, 8192);
313    }
314
315    #[test]
316    fn test_builder_chunk_size_capped() {
317        // Chunk size should be capped at MAX_CHUNK_SIZE
318        let config = ServerConfig::default().chunk_size(u32::MAX);
319
320        assert_eq!(config.chunk_size, MAX_CHUNK_SIZE);
321    }
322
323    #[test]
324    fn test_builder_disable_gop_buffer() {
325        let config = ServerConfig::default().disable_gop_buffer();
326
327        assert!(!config.gop_buffer_enabled);
328    }
329
330    #[test]
331    fn test_builder_connection_timeout() {
332        let config = ServerConfig::default().connection_timeout(Duration::from_secs(30));
333
334        assert_eq!(config.connection_timeout, Duration::from_secs(30));
335    }
336
337    #[test]
338    fn test_builder_idle_timeout() {
339        let config = ServerConfig::default().idle_timeout(Duration::from_secs(120));
340
341        assert_eq!(config.idle_timeout, Duration::from_secs(120));
342    }
343
344    #[test]
345    fn test_builder_chaining() {
346        let addr: SocketAddr = "127.0.0.1:1935".parse().unwrap();
347        let config = ServerConfig::default()
348            .bind(addr)
349            .max_connections(50)
350            .chunk_size(4096)
351            .connection_timeout(Duration::from_secs(5))
352            .idle_timeout(Duration::from_secs(30))
353            .disable_gop_buffer();
354
355        assert_eq!(config.bind_addr, addr);
356        assert_eq!(config.max_connections, 50);
357        assert_eq!(config.chunk_size, 4096);
358        assert_eq!(config.connection_timeout, Duration::from_secs(5));
359        assert_eq!(config.idle_timeout, Duration::from_secs(30));
360        assert!(!config.gop_buffer_enabled);
361    }
362
363    #[test]
364    fn test_enhanced_rtmp_mode() {
365        let config = ServerConfig::default().enhanced_rtmp(EnhancedRtmpMode::EnhancedOnly);
366        assert_eq!(config.enhanced_rtmp, EnhancedRtmpMode::EnhancedOnly);
367
368        let config = ServerConfig::default().enhanced_rtmp(EnhancedRtmpMode::LegacyOnly);
369        assert_eq!(config.enhanced_rtmp, EnhancedRtmpMode::LegacyOnly);
370    }
371
372    #[test]
373    fn test_enhanced_server_capabilities_default() {
374        let caps = EnhancedServerCapabilities::default();
375
376        assert!(!caps.reconnect);
377        assert!(!caps.multitrack);
378        assert!(caps.modex);
379        assert!(!caps.video_codecs.is_empty());
380        assert!(!caps.audio_codecs.is_empty());
381
382        // Check default video codecs
383        assert!(caps
384            .video_codecs
385            .iter()
386            .any(|(c, _)| *c == VideoFourCc::Avc));
387        assert!(caps
388            .video_codecs
389            .iter()
390            .any(|(c, _)| *c == VideoFourCc::Hevc));
391        assert!(caps
392            .video_codecs
393            .iter()
394            .any(|(c, _)| *c == VideoFourCc::Av1));
395
396        // Check default audio codecs
397        assert!(caps
398            .audio_codecs
399            .iter()
400            .any(|(c, _)| *c == AudioFourCc::Aac));
401        assert!(caps
402            .audio_codecs
403            .iter()
404            .any(|(c, _)| *c == AudioFourCc::Opus));
405    }
406
407    #[test]
408    fn test_enhanced_server_capabilities_minimal() {
409        let caps = EnhancedServerCapabilities::minimal();
410
411        assert!(!caps.reconnect);
412        assert!(!caps.multitrack);
413        assert!(caps.modex);
414        assert!(caps.video_codecs.is_empty());
415        assert!(caps.audio_codecs.is_empty());
416    }
417
418    #[test]
419    fn test_enhanced_server_capabilities_builder() {
420        let caps = EnhancedServerCapabilities::minimal()
421            .with_video_codec(VideoFourCc::Hevc, FourCcCapability::full())
422            .with_audio_codec(AudioFourCc::Opus, FourCcCapability::decode())
423            .with_reconnect()
424            .with_multitrack();
425
426        assert!(caps.reconnect);
427        assert!(caps.multitrack);
428        assert_eq!(caps.video_codecs.len(), 1);
429        assert_eq!(caps.audio_codecs.len(), 1);
430
431        let (codec, cap) = &caps.video_codecs[0];
432        assert_eq!(*codec, VideoFourCc::Hevc);
433        assert!(cap.can_decode());
434        assert!(cap.can_encode());
435        assert!(cap.can_forward());
436    }
437
438    #[test]
439    fn test_enhanced_server_capabilities_to_caps_ex() {
440        let caps = EnhancedServerCapabilities::default();
441        let caps_ex = caps.to_caps_ex();
442
443        assert!(!caps_ex.supports_reconnect());
444        assert!(!caps_ex.supports_multitrack());
445        assert!(caps_ex.supports_modex());
446
447        let caps_full = EnhancedServerCapabilities::default()
448            .with_reconnect()
449            .with_multitrack();
450        let caps_ex = caps_full.to_caps_ex();
451
452        assert!(caps_ex.supports_reconnect());
453        assert!(caps_ex.supports_multitrack());
454        assert!(caps_ex.supports_modex());
455    }
456
457    #[test]
458    fn test_config_with_enhanced_capabilities() {
459        let caps = EnhancedServerCapabilities::minimal()
460            .with_video_codec(VideoFourCc::Av1, FourCcCapability::forward());
461
462        let config = ServerConfig::default()
463            .enhanced_rtmp(EnhancedRtmpMode::Auto)
464            .enhanced_capabilities(caps);
465
466        assert_eq!(config.enhanced_rtmp, EnhancedRtmpMode::Auto);
467        assert_eq!(config.enhanced_capabilities.video_codecs.len(), 1);
468    }
469}