Skip to content
File

Blob: firmware/vendor/str0m/tests/config-validation.rs

rust553 lines
1//! Tests for configuration edge cases and validation.
2 
3use std::net::Ipv4Addr;
4use std::time::{Duration, Instant};
5 
6use str0m::media::{Direction, MediaKind};
7use str0m::{Event, RtcConfig, RtcError};
8use tracing::info_span;
9 
10mod common;
11use common::{Peer, TestRtc, init_crypto_default, init_log, negotiate, progress};
12 
13/// Test set_reordering_size_audio() and set_reordering_size_video() with custom sizes.
14#[test]
15fn config_reordering_size_custom() -> Result<(), RtcError> {
16 init_log();
17 init_crypto_default();
18 
19 // Verify config builder correctly sets and retrieves values
20 let config = RtcConfig::new()
21 .set_reordering_size_audio(20)
22 .set_reordering_size_video(50);
23 
24 // Verify the config has correct values before building
25 assert_eq!(
26 config.reordering_size_audio(),
27 20,
28 "Audio reordering size should be 20"
29 );
30 assert_eq!(
31 config.reordering_size_video(),
32 50,
33 "Video reordering size should be 50"
34 );
35 
36 // Verify default values are different
37 let default_config = RtcConfig::new();
38 assert_eq!(
39 default_config.reordering_size_audio(),
40 15,
41 "Default audio reordering should be 15"
42 );
43 assert_eq!(
44 default_config.reordering_size_video(),
45 30,
46 "Default video reordering should be 30"
47 );
48 
49 // Build and verify the Rtc works with custom config
50 let rtc = config.build(Instant::now());
51 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
52 let mut r = TestRtc::new(Peer::Right);
53 
54 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
55 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
56 
57 let mid = negotiate(&mut l, &mut r, |change| {
58 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
59 });
60 
61 loop {
62 if l.is_connected() && r.is_connected() {
63 break;
64 }
65 if l.duration() > Duration::from_secs(5) {
66 panic!("Failed to connect");
67 }
68 progress(&mut l, &mut r)?;
69 }
70 
71 // Send and receive data to verify the config doesn't break functionality
72 let params = l.params_opus();
73 let pt = params.pt();
74 let data = vec![1_u8; 80];
75 
76 for _ in 0..20 {
77 let wallclock = l.start + l.duration();
78 let time = l.duration().into();
79 l.writer(mid)
80 .unwrap()
81 .write(pt, wallclock, time, data.clone())?;
82 progress(&mut l, &mut r)?;
83 }
84 
85 // Verify data was received
86 let received_count = r
87 .events
88 .iter()
89 .filter(|(_, e)| matches!(e, Event::MediaData(_)))
90 .count();
91 
92 assert!(
93 received_count > 10,
94 "Should receive media data with custom reordering config, got {}",
95 received_count
96 );
97 
98 Ok(())
99}
100 
101/// Test enable_raw_packets(true) produces RawPacket events.
102#[test]
103fn config_raw_packets_enabled() -> Result<(), RtcError> {
104 init_log();
105 init_crypto_default();
106 
107 let rtc = RtcConfig::new()
108 .enable_raw_packets(true)
109 .build(Instant::now());
110 
111 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
112 
113 let rtc_r = RtcConfig::new()
114 .enable_raw_packets(true)
115 .build(Instant::now());
116 let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
117 
118 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
119 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
120 
121 let mid = negotiate(&mut l, &mut r, |change| {
122 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
123 });
124 
125 loop {
126 if l.is_connected() && r.is_connected() {
127 break;
128 }
129 if l.duration() > Duration::from_secs(5) {
130 panic!("Failed to connect");
131 }
132 progress(&mut l, &mut r)?;
133 }
134 
135 // Clear previous events
136 l.events.clear();
137 r.events.clear();
138 
139 // Send some audio data
140 let params = l.params_opus();
141 let pt = params.pt();
142 let data = vec![1_u8; 80];
143 
144 for _ in 0..20 {
145 let wallclock = l.start + l.duration();
146 let time = l.duration().into();
147 l.writer(mid)
148 .unwrap()
149 .write(pt, wallclock, time, data.clone())?;
150 progress(&mut l, &mut r)?;
151 }
152 
153 // Check for RawPacket events
154 let raw_packet_count = r
155 .events
156 .iter()
157 .filter(|(_, e)| matches!(e, Event::RawPacket(_)))
158 .count();
159 
160 assert!(
161 raw_packet_count > 0,
162 "Should have RawPacket events with enable_raw_packets(true)"
163 );
164 
165 Ok(())
166}
167 
168/// Test set_stats_interval with custom duration.
169#[test]
170fn config_stats_interval_custom() -> Result<(), RtcError> {
171 init_log();
172 init_crypto_default();
173 
174 let stats_interval = Duration::from_secs(1);
175 let rtc = RtcConfig::new()
176 .set_stats_interval(Some(stats_interval))
177 .build(Instant::now());
178 
179 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
180 
181 let rtc_r = RtcConfig::new()
182 .set_stats_interval(Some(stats_interval))
183 .build(Instant::now());
184 let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
185 
186 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
187 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
188 
189 let mid = negotiate(&mut l, &mut r, |change| {
190 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
191 });
192 
193 loop {
194 if l.is_connected() && r.is_connected() {
195 break;
196 }
197 progress(&mut l, &mut r)?;
198 }
199 
200 let max = l.last.max(r.last);
201 l.last = max;
202 r.last = max;
203 
204 let params = l.params_opus();
205 let pt = params.pt();
206 let data = vec![1_u8; 80];
207 
208 l.set_forced_time_advance(Duration::from_millis(1));
209 r.set_forced_time_advance(Duration::from_millis(1));
210 
211 // Run for 5 seconds to get multiple stats events
212 loop {
213 let wallclock = l.start + l.duration();
214 let time = l.duration().into();
215 l.writer(mid)
216 .unwrap()
217 .write(pt, wallclock, time, data.clone())?;
218 progress(&mut l, &mut r)?;
219 if l.duration() > Duration::from_secs(5) {
220 break;
221 }
222 }
223 
224 // Should have multiple stats events with 1s interval over 5s
225 let peer_stats_count = l
226 .events
227 .iter()
228 .filter(|(_, e)| matches!(e, Event::PeerStats(_)))
229 .count();
230 
231 assert!(
232 peer_stats_count >= 3,
233 "Expected at least 3 PeerStats events with 1s interval over 5s, got {}",
234 peer_stats_count
235 );
236 
237 Ok(())
238}
239 
240#[test]
241fn config_rtcp_report_intervals() {
242 let default_config = RtcConfig::new();
243 assert_eq!(
244 default_config.rtcp_report_interval_audio(),
245 Duration::from_secs(5)
246 );
247 assert_eq!(
248 default_config.rtcp_report_interval_video(),
249 Duration::from_secs(1)
250 );
251 
252 let config = RtcConfig::new()
253 .set_rtcp_report_interval_audio(Duration::from_millis(750))
254 .set_rtcp_report_interval_video(Duration::from_millis(500));
255 assert_eq!(
256 config.rtcp_report_interval_audio(),
257 Duration::from_millis(750)
258 );
259 assert_eq!(
260 config.rtcp_report_interval_video(),
261 Duration::from_millis(500)
262 );
263}
264 
265#[test]
266#[should_panic]
267fn config_rejects_zero_audio_rtcp_report_interval() {
268 RtcConfig::new().set_rtcp_report_interval_audio(Duration::ZERO);
269}
270 
271#[test]
272#[should_panic]
273fn config_rejects_zero_video_rtcp_report_interval() {
274 RtcConfig::new().set_rtcp_report_interval_video(Duration::ZERO);
275}
276 
277/// Test set_stats_interval(None) produces no stats events.
278#[test]
279fn config_stats_disabled() -> Result<(), RtcError> {
280 init_log();
281 init_crypto_default();
282 
283 let rtc = RtcConfig::new()
284 .set_stats_interval(None)
285 .build(Instant::now());
286 
287 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
288 
289 let rtc_r = RtcConfig::new()
290 .set_stats_interval(None)
291 .build(Instant::now());
292 let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
293 
294 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
295 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
296 
297 let mid = negotiate(&mut l, &mut r, |change| {
298 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
299 });
300 
301 loop {
302 if l.is_connected() && r.is_connected() {
303 break;
304 }
305 progress(&mut l, &mut r)?;
306 }
307 
308 let max = l.last.max(r.last);
309 l.last = max;
310 r.last = max;
311 
312 let params = l.params_opus();
313 let pt = params.pt();
314 let data = vec![1_u8; 80];
315 
316 l.set_forced_time_advance(Duration::from_millis(1));
317 r.set_forced_time_advance(Duration::from_millis(1));
318 
319 loop {
320 let wallclock = l.start + l.duration();
321 let time = l.duration().into();
322 l.writer(mid)
323 .unwrap()
324 .write(pt, wallclock, time, data.clone())?;
325 progress(&mut l, &mut r)?;
326 if l.duration() > Duration::from_secs(3) {
327 break;
328 }
329 }
330 
331 let peer_stats_count = l
332 .events
333 .iter()
334 .filter(|(_, e)| matches!(e, Event::PeerStats(_)))
335 .count();
336 
337 assert_eq!(
338 peer_stats_count, 0,
339 "Should have no PeerStats events when stats disabled"
340 );
341 
342 Ok(())
343}
344 
345/// Test set_fingerprint_verification(false) allows connection with wrong fingerprint.
346#[test]
347fn config_fingerprint_verification_disabled() -> Result<(), RtcError> {
348 init_log();
349 init_crypto_default();
350 
351 use str0m::Candidate;
352 use str0m::crypto::Fingerprint;
353 
354 // Create RTCs with fingerprint verification DISABLED
355 let rtc_l = RtcConfig::new()
356 .set_fingerprint_verification(false)
357 .set_rtp_mode(true)
358 .build(Instant::now());
359 
360 let rtc_r = RtcConfig::new()
361 .set_fingerprint_verification(false)
362 .set_rtp_mode(true)
363 .build(Instant::now());
364 
365 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc_l);
366 let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc_r);
367 
368 // Set up candidates
369 let host1 = Candidate::host((Ipv4Addr::new(1, 1, 1, 1), 1000).into(), "udp").unwrap();
370 let host2 = Candidate::host((Ipv4Addr::new(2, 2, 2, 2), 2000).into(), "udp").unwrap();
371 l.add_local_candidate(host1.clone()).unwrap();
372 l.add_remote_candidate(host2.clone());
373 r.add_local_candidate(host2).unwrap();
374 r.add_remote_candidate(host1);
375 
376 // Create CORRUPTED fingerprints (all zeros - definitely wrong)
377 let corrupted_fingerprint = Fingerprint {
378 hash_func: "sha-256".to_string(),
379 bytes: vec![0u8; 32], // Wrong fingerprint!
380 };
381 
382 // Set the WRONG fingerprints as remote (this would fail with verification enabled)
383 l.direct_api()
384 .set_remote_fingerprint(corrupted_fingerprint.clone());
385 r.direct_api().set_remote_fingerprint(corrupted_fingerprint);
386 
387 // Exchange ICE credentials
388 let creds_l = l.direct_api().local_ice_credentials();
389 let creds_r = r.direct_api().local_ice_credentials();
390 l.direct_api().set_remote_ice_credentials(creds_r);
391 r.direct_api().set_remote_ice_credentials(creds_l);
392 
393 l.direct_api().set_ice_controlling(true);
394 r.direct_api().set_ice_controlling(false);
395 
396 // Start DTLS - this should succeed despite wrong fingerprints
397 // because verification is disabled
398 l.direct_api().start_dtls(true).unwrap();
399 r.direct_api().start_dtls(false).unwrap();
400 
401 l.direct_api().start_sctp(true);
402 r.direct_api().start_sctp(false);
403 
404 // Connection should succeed despite corrupted fingerprints
405 loop {
406 if l.is_connected() && r.is_connected() {
407 break;
408 }
409 if l.duration() > Duration::from_secs(5) {
410 panic!("Failed to connect - fingerprint verification should be disabled");
411 }
412 progress(&mut l, &mut r)?;
413 }
414 
415 // Verify we actually connected
416 assert!(l.is_connected(), "L should be connected");
417 assert!(r.is_connected(), "R should be connected");
418 
419 Ok(())
420}
421 
422/// Test that RtcConfig can be cloned and used for multiple instances.
423#[test]
424fn config_clone_multiple_instances() -> Result<(), RtcError> {
425 init_log();
426 init_crypto_default();
427 
428 // Create a config with custom settings (not ice_lite since both can't be ice_lite)
429 let config = RtcConfig::new()
430 .set_reordering_size_audio(20)
431 .set_reordering_size_video(40);
432 
433 // Clone the config and create multiple instances
434 let rtc1 = config.clone().build(Instant::now());
435 let rtc2 = config.build(Instant::now());
436 
437 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc1);
438 let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc2);
439 
440 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
441 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
442 
443 let (offer, pending) = l.span.in_scope(|| {
444 let mut change = l.rtc.sdp_api();
445 let _ = change.add_channel("test".into());
446 change.apply().unwrap()
447 });
448 
449 let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?;
450 l.span
451 .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?;
452 
453 loop {
454 if l.is_connected() && r.is_connected() {
455 break;
456 }
457 if l.duration() > Duration::from_secs(5) {
458 panic!("Failed to connect with cloned config");
459 }
460 progress(&mut l, &mut r)?;
461 }
462 
463 Ok(())
464}
465 
466/// Test set_send_buffer_audio and set_send_buffer_video configuration.
467#[test]
468fn config_send_buffer_sizes() -> Result<(), RtcError> {
469 init_log();
470 init_crypto_default();
471 
472 // Verify config builder correctly sets and retrieves values
473 let config = RtcConfig::new()
474 .set_send_buffer_audio(100)
475 .set_send_buffer_video(2000);
476 
477 // Verify the config has correct values before building
478 assert_eq!(
479 config.send_buffer_audio(),
480 100,
481 "Audio send buffer should be 100"
482 );
483 assert_eq!(
484 config.send_buffer_video(),
485 2000,
486 "Video send buffer should be 2000"
487 );
488 
489 // Verify default values are different
490 let default_config = RtcConfig::new();
491 assert_eq!(
492 default_config.send_buffer_audio(),
493 50,
494 "Default audio send buffer should be 50"
495 );
496 assert_eq!(
497 default_config.send_buffer_video(),
498 1000,
499 "Default video send buffer should be 1000"
500 );
501 
502 // Build and verify the Rtc works with custom config
503 let rtc = config.build(Instant::now());
504 let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc);
505 let mut r = TestRtc::new(Peer::Right);
506 
507 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
508 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
509 
510 let mid = negotiate(&mut l, &mut r, |change| {
511 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
512 });
513 
514 loop {
515 if l.is_connected() && r.is_connected() {
516 break;
517 }
518 if l.duration() > Duration::from_secs(5) {
519 panic!("Failed to connect");
520 }
521 progress(&mut l, &mut r)?;
522 }
523 
524 // Send and receive data to verify the config doesn't break functionality
525 let params = l.params_opus();
526 let pt = params.pt();
527 let data = vec![1_u8; 80];
528 
529 for _ in 0..20 {
530 let wallclock = l.start + l.duration();
531 let time = l.duration().into();
532 l.writer(mid)
533 .unwrap()
534 .write(pt, wallclock, time, data.clone())?;
535 progress(&mut l, &mut r)?;
536 }
537 
538 // Verify data was received
539 let received_count = r
540 .events
541 .iter()
542 .filter(|(_, e)| matches!(e, Event::MediaData(_)))
543 .count();
544 
545 assert!(
546 received_count > 10,
547 "Should receive media data with custom send buffer config, got {}",
548 received_count
549 );
550 
551 Ok(())
552}