File
Blob: firmware/vendor/str0m/tests/ice-candidates.rs
| 1 | //! Tests for ICE candidate handling and configuration. |
| 2 | |
| 3 | use std::net::Ipv4Addr; |
| 4 | use std::time::{Duration, Instant}; |
| 5 | |
| 6 | use str0m::RtcConfig; |
| 7 | use str0m::RtcError; |
| 8 | use str0m::ice::IceCreds; |
| 9 | use tracing::info_span; |
| 10 | |
| 11 | mod common; |
| 12 | use common::{Peer, TestRtc, init_crypto_default, init_log, progress}; |
| 13 | |
| 14 | /// Test connection with only host candidates. |
| 15 | #[test] |
| 16 | fn ice_candidate_types_host_only() -> Result<(), RtcError> { |
| 17 | init_log(); |
| 18 | init_crypto_default(); |
| 19 | |
| 20 | let mut l = TestRtc::new(Peer::Left); |
| 21 | let mut r = TestRtc::new(Peer::Right); |
| 22 | |
| 23 | // Add only host candidates (no srflx or relay) |
| 24 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 25 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 26 | |
| 27 | let (offer, pending) = l.span.in_scope(|| { |
| 28 | let mut change = l.rtc.sdp_api(); |
| 29 | let _ = change.add_channel("test".into()); |
| 30 | change.apply().unwrap() |
| 31 | }); |
| 32 | |
| 33 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 34 | l.span |
| 35 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 36 | |
| 37 | // Should connect using host candidates |
| 38 | loop { |
| 39 | if l.is_connected() && r.is_connected() { |
| 40 | break; |
| 41 | } |
| 42 | if l.duration() > Duration::from_secs(5) { |
| 43 | panic!("Failed to connect with host-only candidates"); |
| 44 | } |
| 45 | progress(&mut l, &mut r)?; |
| 46 | } |
| 47 | |
| 48 | Ok(()) |
| 49 | } |
| 50 | |
| 51 | /// Test trickle ICE - connect with relay candidates, then trickle in host candidates. |
| 52 | /// Verifies that ICE switches to the better (host) candidates after they're added. |
| 53 | #[test] |
| 54 | fn ice_trickle_incremental_candidates() -> Result<(), RtcError> { |
| 55 | use std::net::SocketAddr; |
| 56 | use str0m::{Candidate, Output}; |
| 57 | |
| 58 | init_log(); |
| 59 | init_crypto_default(); |
| 60 | |
| 61 | let mut l = TestRtc::new(Peer::Left); |
| 62 | let mut r = TestRtc::new(Peer::Right); |
| 63 | |
| 64 | // Define addresses for relay and host candidates |
| 65 | // Relay addresses (TURN allocated addresses, lower priority) |
| 66 | let l_relay_addr: SocketAddr = (Ipv4Addr::new(10, 0, 0, 1), 10000).into(); |
| 67 | let r_relay_addr: SocketAddr = (Ipv4Addr::new(10, 0, 0, 2), 20000).into(); |
| 68 | |
| 69 | // Local addresses (base addresses for relay candidates) |
| 70 | let l_local_addr: SocketAddr = (Ipv4Addr::new(192, 168, 1, 1), 1000).into(); |
| 71 | let r_local_addr: SocketAddr = (Ipv4Addr::new(192, 168, 1, 2), 2000).into(); |
| 72 | |
| 73 | // Host addresses (higher priority, will be trickled later) |
| 74 | let l_host_addr: SocketAddr = (Ipv4Addr::new(1, 1, 1, 1), 1000).into(); |
| 75 | let r_host_addr: SocketAddr = (Ipv4Addr::new(2, 2, 2, 2), 2000).into(); |
| 76 | |
| 77 | // Create relay candidates (lower priority than host) |
| 78 | let l_relay = Candidate::relayed(l_relay_addr, l_local_addr, "udp").unwrap(); |
| 79 | let r_relay = Candidate::relayed(r_relay_addr, r_local_addr, "udp").unwrap(); |
| 80 | |
| 81 | // Add relay candidates initially |
| 82 | l.rtc.add_local_candidate(l_relay.clone()).unwrap(); |
| 83 | r.rtc.add_local_candidate(r_relay.clone()).unwrap(); |
| 84 | |
| 85 | // Exchange relay candidates |
| 86 | l.rtc.add_remote_candidate(r_relay.clone()); |
| 87 | r.rtc.add_remote_candidate(l_relay.clone()); |
| 88 | |
| 89 | // Create offer/answer |
| 90 | let (offer, pending) = l.span.in_scope(|| { |
| 91 | let mut change = l.rtc.sdp_api(); |
| 92 | let _ = change.add_channel("test".into()); |
| 93 | change.apply().unwrap() |
| 94 | }); |
| 95 | |
| 96 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 97 | l.span |
| 98 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 99 | |
| 100 | // Connect using relay candidates |
| 101 | loop { |
| 102 | if l.is_connected() && r.is_connected() { |
| 103 | break; |
| 104 | } |
| 105 | if l.duration() > Duration::from_secs(5) { |
| 106 | panic!("Failed to connect with relay candidates"); |
| 107 | } |
| 108 | progress(&mut l, &mut r)?; |
| 109 | } |
| 110 | |
| 111 | // Get the data channel id |
| 112 | let channel_id = l |
| 113 | .events |
| 114 | .iter() |
| 115 | .find_map(|(_, e)| { |
| 116 | if let str0m::Event::ChannelOpen(id, _) = e { |
| 117 | Some(*id) |
| 118 | } else { |
| 119 | None |
| 120 | } |
| 121 | }) |
| 122 | .expect("Should have opened a data channel"); |
| 123 | |
| 124 | // Send data to trigger a Transmit and capture the source address |
| 125 | l.rtc |
| 126 | .channel(channel_id) |
| 127 | .unwrap() |
| 128 | .write(true, b"test") |
| 129 | .unwrap(); |
| 130 | |
| 131 | let mut initial_send_addr: Option<SocketAddr> = None; |
| 132 | l.rtc.handle_input(str0m::Input::Timeout(l.last)).unwrap(); |
| 133 | loop { |
| 134 | match l.rtc.poll_output() { |
| 135 | Ok(Output::Transmit(t)) => { |
| 136 | initial_send_addr = Some(t.source); |
| 137 | break; |
| 138 | } |
| 139 | Ok(Output::Timeout(_)) => break, |
| 140 | Ok(_) => continue, |
| 141 | Err(e) => return Err(e), |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | let initial_send_addr = initial_send_addr.expect("Should have a Transmit after sending data"); |
| 146 | assert_eq!( |
| 147 | initial_send_addr, l_relay_addr, |
| 148 | "Initial send address should be relay candidate" |
| 149 | ); |
| 150 | |
| 151 | // Now trickle in host candidates (higher priority) |
| 152 | let l_host = Candidate::host(l_host_addr, "udp").unwrap(); |
| 153 | let r_host = Candidate::host(r_host_addr, "udp").unwrap(); |
| 154 | |
| 155 | let l_host_added = l.rtc.add_local_candidate(l_host.clone()).unwrap().clone(); |
| 156 | let r_host_added = r.rtc.add_local_candidate(r_host.clone()).unwrap().clone(); |
| 157 | |
| 158 | // Exchange host candidates between peers (simulating trickle ICE signaling) |
| 159 | l.rtc.add_remote_candidate(r_host_added); |
| 160 | r.rtc.add_remote_candidate(l_host_added); |
| 161 | |
| 162 | // Progress to allow ICE to discover and switch to better candidates |
| 163 | for _ in 0..100 { |
| 164 | progress(&mut l, &mut r)?; |
| 165 | } |
| 166 | |
| 167 | // Send data again and capture the new source address - should have switched to host |
| 168 | l.rtc |
| 169 | .channel(channel_id) |
| 170 | .unwrap() |
| 171 | .write(true, b"test2") |
| 172 | .unwrap(); |
| 173 | |
| 174 | let mut final_send_addr: Option<SocketAddr> = None; |
| 175 | l.rtc.handle_input(str0m::Input::Timeout(l.last)).unwrap(); |
| 176 | loop { |
| 177 | match l.rtc.poll_output() { |
| 178 | Ok(Output::Transmit(t)) => { |
| 179 | final_send_addr = Some(t.source); |
| 180 | break; |
| 181 | } |
| 182 | Ok(Output::Timeout(_)) => break, |
| 183 | Ok(_) => continue, |
| 184 | Err(e) => return Err(e), |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | let final_send_addr = |
| 189 | final_send_addr.expect("Should have a Transmit after sending data post-trickle"); |
| 190 | |
| 191 | // Verify that ICE switched from relay to host candidates |
| 192 | assert_eq!( |
| 193 | final_send_addr, l_host_addr, |
| 194 | "After trickle, send address should switch to host candidate" |
| 195 | ); |
| 196 | |
| 197 | // Verify the switch actually happened |
| 198 | assert_ne!( |
| 199 | initial_send_addr, final_send_addr, |
| 200 | "Send address should have changed from relay ({}) to host ({})", |
| 201 | initial_send_addr, final_send_addr |
| 202 | ); |
| 203 | |
| 204 | Ok(()) |
| 205 | } |
| 206 | |
| 207 | /// Test custom ICE credentials via set_local_ice_credentials(). |
| 208 | #[test] |
| 209 | fn ice_custom_credentials() -> Result<(), RtcError> { |
| 210 | init_log(); |
| 211 | init_crypto_default(); |
| 212 | |
| 213 | let custom_creds = IceCreds { |
| 214 | ufrag: "customufrag123".into(), |
| 215 | pass: "custompassword456789012".into(), |
| 216 | }; |
| 217 | |
| 218 | let rtc = RtcConfig::new() |
| 219 | .set_local_ice_credentials(custom_creds.clone()) |
| 220 | .build(Instant::now()); |
| 221 | |
| 222 | let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc); |
| 223 | let mut r = TestRtc::new(Peer::Right); |
| 224 | |
| 225 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 226 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 227 | |
| 228 | // Verify custom credentials are used |
| 229 | let actual_creds = l._local_ice_creds(); |
| 230 | assert_eq!( |
| 231 | actual_creds.ufrag, custom_creds.ufrag, |
| 232 | "Custom ufrag should be used" |
| 233 | ); |
| 234 | assert_eq!( |
| 235 | actual_creds.pass, custom_creds.pass, |
| 236 | "Custom password should be used" |
| 237 | ); |
| 238 | |
| 239 | let (offer, pending) = l.span.in_scope(|| { |
| 240 | let mut change = l.rtc.sdp_api(); |
| 241 | let _ = change.add_channel("test".into()); |
| 242 | change.apply().unwrap() |
| 243 | }); |
| 244 | |
| 245 | // Verify custom ufrag appears in offer SDP |
| 246 | let offer_str = offer.to_string(); |
| 247 | assert!( |
| 248 | offer_str.contains(&custom_creds.ufrag), |
| 249 | "Offer SDP should contain custom ufrag" |
| 250 | ); |
| 251 | |
| 252 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 253 | l.span |
| 254 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 255 | |
| 256 | loop { |
| 257 | if l.is_connected() && r.is_connected() { |
| 258 | break; |
| 259 | } |
| 260 | if l.duration() > Duration::from_secs(5) { |
| 261 | panic!("Failed to connect with custom credentials"); |
| 262 | } |
| 263 | progress(&mut l, &mut r)?; |
| 264 | } |
| 265 | |
| 266 | Ok(()) |
| 267 | } |
| 268 | |
| 269 | /// Test set_initial_stun_rto() configuration by measuring retransmission timing. |
| 270 | /// Uses a short RTO and verifies retransmissions happen at the expected interval. |
| 271 | #[test] |
| 272 | fn ice_stun_timeout_initial_rto() -> Result<(), RtcError> { |
| 273 | use str0m::Output; |
| 274 | |
| 275 | init_log(); |
| 276 | init_crypto_default(); |
| 277 | |
| 278 | // Set a short initial RTO of 50ms (default is 250ms) |
| 279 | let custom_rto = Duration::from_millis(50); |
| 280 | let mut config = RtcConfig::new(); |
| 281 | config.set_initial_stun_rto(custom_rto); |
| 282 | let start = Instant::now(); |
| 283 | let rtc = config.build(start); |
| 284 | |
| 285 | let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc); |
| 286 | let mut r = TestRtc::new(Peer::Right); |
| 287 | |
| 288 | // Sync TestRtc time with Rtc creation time |
| 289 | l.start = start; |
| 290 | l.last = start; |
| 291 | |
| 292 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 293 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 294 | |
| 295 | // Set up SDP exchange |
| 296 | let (offer, pending) = l.span.in_scope(|| { |
| 297 | let mut change = l.rtc.sdp_api(); |
| 298 | let _ = change.add_channel("test".into()); |
| 299 | change.apply().unwrap() |
| 300 | }); |
| 301 | |
| 302 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 303 | l.span |
| 304 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 305 | |
| 306 | // Track transmit times from L only (don't deliver to R, so L will retransmit) |
| 307 | let mut transmit_times: Vec<Instant> = Vec::new(); |
| 308 | |
| 309 | // Progress L only, capture STUN transmit times |
| 310 | for _ in 0..30 { |
| 311 | l.rtc.handle_input(str0m::Input::Timeout(l.last)).unwrap(); |
| 312 | |
| 313 | loop { |
| 314 | match l.rtc.poll_output()? { |
| 315 | Output::Transmit(_) => { |
| 316 | transmit_times.push(l.last); |
| 317 | } |
| 318 | Output::Timeout(t) => { |
| 319 | l.last = t; |
| 320 | break; |
| 321 | } |
| 322 | Output::Event(_) => {} |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | // Stop once we have enough samples |
| 327 | if transmit_times.len() >= 3 { |
| 328 | break; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | assert!( |
| 333 | transmit_times.len() >= 2, |
| 334 | "Should have at least 2 STUN transmissions, got {}", |
| 335 | transmit_times.len() |
| 336 | ); |
| 337 | |
| 338 | // Check the interval between first and second transmit matches initial RTO |
| 339 | let first_interval = transmit_times[1].duration_since(transmit_times[0]); |
| 340 | |
| 341 | // Allow some tolerance (35-65ms for 50ms RTO) |
| 342 | let min_expected = custom_rto - Duration::from_millis(15); |
| 343 | let max_expected = custom_rto + Duration::from_millis(15); |
| 344 | |
| 345 | assert!( |
| 346 | first_interval >= min_expected && first_interval <= max_expected, |
| 347 | "First retransmit interval should be ~{}ms (initial RTO), got {}ms", |
| 348 | custom_rto.as_millis(), |
| 349 | first_interval.as_millis() |
| 350 | ); |
| 351 | |
| 352 | Ok(()) |
| 353 | } |
| 354 | |
| 355 | /// Test set_max_stun_rto() configuration by verifying retransmit intervals are capped. |
| 356 | #[test] |
| 357 | fn ice_stun_timeout_max_rto() -> Result<(), RtcError> { |
| 358 | use str0m::Output; |
| 359 | |
| 360 | init_log(); |
| 361 | init_crypto_default(); |
| 362 | |
| 363 | // Set initial RTO to 100ms and max RTO to 150ms |
| 364 | // Without max cap, RTO would double: 100 -> 200 -> 400... |
| 365 | // With max 150ms, it should cap at: 100 -> 150 -> 150... |
| 366 | let initial_rto = Duration::from_millis(100); |
| 367 | let max_rto = Duration::from_millis(150); |
| 368 | |
| 369 | let mut config = RtcConfig::new(); |
| 370 | config.set_initial_stun_rto(initial_rto); |
| 371 | config.set_max_stun_rto(max_rto); |
| 372 | let start = Instant::now(); |
| 373 | let rtc = config.build(start); |
| 374 | |
| 375 | let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc); |
| 376 | let mut r = TestRtc::new(Peer::Right); |
| 377 | |
| 378 | // Sync TestRtc time with Rtc creation time |
| 379 | l.start = start; |
| 380 | l.last = start; |
| 381 | |
| 382 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 383 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 384 | |
| 385 | let (offer, pending) = l.span.in_scope(|| { |
| 386 | let mut change = l.rtc.sdp_api(); |
| 387 | let _ = change.add_channel("test".into()); |
| 388 | change.apply().unwrap() |
| 389 | }); |
| 390 | |
| 391 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 392 | l.span |
| 393 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 394 | |
| 395 | // Track transmit times from L only (don't deliver to R) |
| 396 | let mut transmit_times: Vec<Instant> = Vec::new(); |
| 397 | |
| 398 | for _ in 0..50 { |
| 399 | l.rtc.handle_input(str0m::Input::Timeout(l.last)).unwrap(); |
| 400 | |
| 401 | loop { |
| 402 | match l.rtc.poll_output()? { |
| 403 | Output::Transmit(_) => { |
| 404 | transmit_times.push(l.last); |
| 405 | } |
| 406 | Output::Timeout(t) => { |
| 407 | l.last = t; |
| 408 | break; |
| 409 | } |
| 410 | Output::Event(_) => {} |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | if transmit_times.len() >= 4 { |
| 415 | break; |
| 416 | } |
| 417 | } |
| 418 | |
| 419 | assert!( |
| 420 | transmit_times.len() >= 4, |
| 421 | "Should have at least 4 transmissions, got {}", |
| 422 | transmit_times.len() |
| 423 | ); |
| 424 | |
| 425 | // Check intervals - first should be ~100ms, subsequent should be capped at ~150ms |
| 426 | let interval_1_2 = transmit_times[1].duration_since(transmit_times[0]); |
| 427 | let interval_2_3 = transmit_times[2].duration_since(transmit_times[1]); |
| 428 | let interval_3_4 = transmit_times[3].duration_since(transmit_times[2]); |
| 429 | |
| 430 | // First interval should be around initial RTO (100ms) |
| 431 | assert!( |
| 432 | interval_1_2 >= Duration::from_millis(85) && interval_1_2 <= Duration::from_millis(115), |
| 433 | "First interval should be ~100ms (initial RTO), got {}ms", |
| 434 | interval_1_2.as_millis() |
| 435 | ); |
| 436 | |
| 437 | // Later intervals should be capped at max RTO (150ms), not doubled (200ms) |
| 438 | // Allow tolerance for timing |
| 439 | let max_allowed = max_rto + Duration::from_millis(20); |
| 440 | assert!( |
| 441 | interval_2_3 <= max_allowed, |
| 442 | "Second interval should be capped at ~{}ms (max RTO), got {}ms", |
| 443 | max_rto.as_millis(), |
| 444 | interval_2_3.as_millis() |
| 445 | ); |
| 446 | assert!( |
| 447 | interval_3_4 <= max_allowed, |
| 448 | "Third interval should be capped at ~{}ms (max RTO), got {}ms", |
| 449 | max_rto.as_millis(), |
| 450 | interval_3_4.as_millis() |
| 451 | ); |
| 452 | |
| 453 | Ok(()) |
| 454 | } |
| 455 | |
| 456 | /// Test set_max_stun_retransmits() configuration by counting actual retransmissions. |
| 457 | #[test] |
| 458 | fn ice_stun_max_retransmits() -> Result<(), RtcError> { |
| 459 | use str0m::Output; |
| 460 | |
| 461 | init_log(); |
| 462 | init_crypto_default(); |
| 463 | |
| 464 | // Set max retransmits to 3 (default is 9) and short RTOs for faster test |
| 465 | let max_retransmits = 3; |
| 466 | let mut config = RtcConfig::new(); |
| 467 | config.set_max_stun_retransmits(max_retransmits); |
| 468 | config.set_initial_stun_rto(Duration::from_millis(20)); |
| 469 | config.set_max_stun_rto(Duration::from_millis(40)); |
| 470 | let start = Instant::now(); |
| 471 | let rtc = config.build(start); |
| 472 | |
| 473 | let mut l = TestRtc::new_with_rtc(info_span!("L"), rtc); |
| 474 | let mut r = TestRtc::new(Peer::Right); |
| 475 | |
| 476 | // Sync TestRtc time with Rtc creation time |
| 477 | l.start = start; |
| 478 | l.last = start; |
| 479 | |
| 480 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 481 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 482 | |
| 483 | let (offer, pending) = l.span.in_scope(|| { |
| 484 | let mut change = l.rtc.sdp_api(); |
| 485 | let _ = change.add_channel("test".into()); |
| 486 | change.apply().unwrap() |
| 487 | }); |
| 488 | |
| 489 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 490 | l.span |
| 491 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 492 | |
| 493 | // Count transmissions from L (without delivering to R) |
| 494 | let mut transmit_count = 0; |
| 495 | |
| 496 | for _ in 0..100 { |
| 497 | l.rtc.handle_input(str0m::Input::Timeout(l.last)).unwrap(); |
| 498 | |
| 499 | loop { |
| 500 | match l.rtc.poll_output()? { |
| 501 | Output::Transmit(_) => { |
| 502 | transmit_count += 1; |
| 503 | } |
| 504 | Output::Timeout(t) => { |
| 505 | l.last = t; |
| 506 | break; |
| 507 | } |
| 508 | Output::Event(_) => {} |
| 509 | } |
| 510 | } |
| 511 | |
| 512 | // Give enough time for all retransmits to happen |
| 513 | if l.duration() > Duration::from_millis(500) { |
| 514 | break; |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | // With max_retransmits=3, we should see limited retransmissions |
| 519 | // The exact count depends on implementation details, but should be bounded |
| 520 | // Key verification: count should be <= max_retransmits + 1 (initial + retransmits) |
| 521 | let expected_max = (max_retransmits + 1) as usize + 2; // +2 for timing tolerance |
| 522 | |
| 523 | assert!( |
| 524 | transmit_count <= expected_max, |
| 525 | "Transmit count should be bounded by max_retransmits setting. \ |
| 526 | Expected at most {} (initial + {} retransmits + tolerance), got {}", |
| 527 | expected_max, |
| 528 | max_retransmits, |
| 529 | transmit_count |
| 530 | ); |
| 531 | |
| 532 | // Should have at least some transmissions |
| 533 | assert!( |
| 534 | transmit_count >= 2, |
| 535 | "Should have at least 2 transmissions (initial + at least 1 retransmit), got {}", |
| 536 | transmit_count |
| 537 | ); |
| 538 | |
| 539 | Ok(()) |
| 540 | } |
| 541 | |
| 542 | /// Test ICE lite mode connectivity. |
| 543 | #[test] |
| 544 | fn ice_lite_mode() -> Result<(), RtcError> { |
| 545 | init_log(); |
| 546 | init_crypto_default(); |
| 547 | |
| 548 | let mut l = TestRtc::new(Peer::Left); |
| 549 | let rtc = RtcConfig::new().set_ice_lite(true).build(Instant::now()); |
| 550 | let mut r = TestRtc::new_with_rtc(info_span!("R"), rtc); |
| 551 | |
| 552 | l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into()); |
| 553 | r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into()); |
| 554 | |
| 555 | let (offer, pending) = l.span.in_scope(|| { |
| 556 | let mut change = l.rtc.sdp_api(); |
| 557 | let _ = change.add_channel("test".into()); |
| 558 | change.apply().unwrap() |
| 559 | }); |
| 560 | |
| 561 | let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?; |
| 562 | l.span |
| 563 | .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?; |
| 564 | |
| 565 | loop { |
| 566 | if l.is_connected() && r.is_connected() { |
| 567 | break; |
| 568 | } |
| 569 | if l.duration() > Duration::from_secs(5) { |
| 570 | panic!("Failed to connect with ICE lite"); |
| 571 | } |
| 572 | progress(&mut l, &mut r)?; |
| 573 | } |
| 574 | |
| 575 | Ok(()) |
| 576 | } |