| ... | ... | @@ -292,6 +292,193 @@ fn peers_meet_through_a_relay() { |
| 292 | 292 | until(&grace, <[Peer]>::is_empty); |
| 293 | 293 | } |
| 294 | 294 | |
| 295 | #[test] |
| 296 | fn relay_timeouts_reopen_with_fresh_keys() { |
| 297 | use ::relay::ws::{self, Message}; |
| 298 | |
| 299 | let (url, upstream) = relay(Default::default()); |
| 300 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); |
| 301 | let delayed = format!("ws://{}", listener.local_addr().unwrap()); |
| 302 | listener.set_nonblocking(true).unwrap(); |
| 303 | let (opening, openings) = mpsc::channel(); |
| 304 | let proxy = thread::spawn(move || { |
| 305 | for attempt in 0..3 { |
| 306 | let deadline = Instant::now() + Duration::from_secs(15); |
| 307 | let mut client = loop { |
| 308 | match listener.accept() { |
| 309 | Ok((client, _)) => break client, |
| 310 | Err(error) if error.kind() == io::ErrorKind::WouldBlock => { |
| 311 | assert!(Instant::now() < deadline, "the relay room never reopened"); |
| 312 | thread::sleep(Duration::from_millis(20)); |
| 313 | } |
| 314 | Err(error) => panic!("{error}"), |
| 315 | } |
| 316 | }; |
| 317 | client.set_nonblocking(false).unwrap(); |
| 318 | client |
| 319 | .set_read_timeout(Some(Duration::from_secs(15))) |
| 320 | .unwrap(); |
| 321 | let mut server = TcpStream::connect(upstream).unwrap(); |
| 322 | let head = ws::head(&mut client).unwrap(); |
| 323 | server.write_all(head.as_bytes()).unwrap(); |
| 324 | let (mut down, mut to_client) = |
| 325 | (server.try_clone().unwrap(), client.try_clone().unwrap()); |
| 326 | let replies = thread::spawn(move || { |
| 327 | let _ = io::copy(&mut down, &mut to_client); |
| 328 | let _ = to_client.shutdown(Shutdown::Both); |
| 329 | }); |
| 330 | let mut messages = ws::Reader::new(client, 1 << 20, true); |
| 331 | let mut first = true; |
| 332 | while let Ok(message) = messages.read() { |
| 333 | let (opcode, data) = match message { |
| 334 | Message::Binary(data) => { |
| 335 | let slot = u32::from_be_bytes(data[..4].try_into().unwrap()); |
| 336 | if first && slot > 0 && slot < ::relay::GROUP { |
| 337 | first = false; |
| 338 | opening.send(data[4..].to_vec()).unwrap(); |
| 339 | // Keep the first opening delayed until its stream is abandoned. |
| 340 | if attempt == 0 { |
| 341 | continue; |
| 342 | } |
| 343 | } |
| 344 | (ws::BINARY, data) |
| 345 | } |
| 346 | Message::Text(text) => (ws::TEXT, text.into_bytes()), |
| 347 | Message::Ping(data) => (ws::PING, data), |
| 348 | Message::Pong => (ws::PONG, vec![]), |
| 349 | Message::Close => break, |
| 350 | }; |
| 351 | if server |
| 352 | .write_all(&ws::frame(opcode, &data, Some([1, 2, 3, 4]))) |
| 353 | .is_err() |
| 354 | { |
| 355 | break; |
| 356 | } |
| 357 | } |
| 358 | let _ = server.shutdown(Shutdown::Both); |
| 359 | replies.join().unwrap(); |
| 360 | } |
| 361 | }); |
| 362 | |
| 363 | let room = Room::Notebook([7; 16]); |
| 364 | let mut host = hello("Host"); |
| 365 | host.serves = Some([8; 16]); |
| 366 | let (met, host_met) = mpsc::channel(); |
| 367 | let (frame, frames) = mpsc::channel(); |
| 368 | let host = Live::start(host, &room, None, Some(&url), move |event| match event { |
| 369 | Event::Met(_, line) => { |
| 370 | let _ = met.send(line.clone()); |
| 371 | } |
| 372 | Event::Frame { kind, body, .. } => { |
| 373 | let _ = frame.send((kind, body.to_vec())); |
| 374 | } |
| 375 | _ => {} |
| 376 | }) |
| 377 | .unwrap(); |
| 378 | let (met, guest_met) = mpsc::channel(); |
| 379 | let (left, guest_left) = mpsc::channel(); |
| 380 | let (frame, guest_frames) = mpsc::channel(); |
| 381 | let guest = Live::start( |
| 382 | hello("Guest"), |
| 383 | &room, |
| 384 | None, |
| 385 | Some(&delayed), |
| 386 | move |event| match event { |
| 387 | Event::Met(_, line) => { |
| 388 | let _ = met.send(line.clone()); |
| 389 | } |
| 390 | Event::Left(_) => { |
| 391 | let _ = left.send(()); |
| 392 | } |
| 393 | Event::Frame { kind, body, .. } => { |
| 394 | let _ = frame.send((kind, body.to_vec())); |
| 395 | } |
| 396 | _ => {} |
| 397 | }, |
| 398 | ) |
| 399 | .unwrap(); |
| 400 | let first = openings.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 401 | assert!(matches!( |
| 402 | host_met.try_recv(), |
| 403 | Err(mpsc::TryRecvError::Empty) |
| 404 | )); |
| 405 | assert!(matches!( |
| 406 | guest_met.try_recv(), |
| 407 | Err(mpsc::TryRecvError::Empty) |
| 408 | )); |
| 409 | let fresh = openings.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 410 | assert_ne!(first, fresh, "the retry reused the abandoned key exchange"); |
| 411 | let host_line = host_met.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 412 | guest_met.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 413 | let before_timeout = guest.shared.state.lock().unwrap().relay.clone().unwrap(); |
| 414 | guest |
| 415 | .shared |
| 416 | .state |
| 417 | .lock() |
| 418 | .unwrap() |
| 419 | .peers |
| 420 | .values() |
| 421 | .next() |
| 422 | .unwrap() |
| 423 | .pipe |
| 424 | .set_read_timeout(Duration::from_secs(1)) |
| 425 | .unwrap(); |
| 426 | host_line.send(kind::EDITS, &40_u64).unwrap(); |
| 427 | let (kind, body) = guest_frames.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 428 | assert_eq!(kind, kind::EDITS); |
| 429 | assert_eq!(minicbor::decode::<u64>(&body).unwrap(), 40); |
| 430 | guest_left.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 431 | let reopened = openings.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 432 | assert_ne!( |
| 433 | fresh, reopened, |
| 434 | "the retry reused the timed-out stream's keys" |
| 435 | ); |
| 436 | host_met.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 437 | let line = guest_met.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 438 | assert_eq!((host.failed(), guest.failed()), (0, 0)); |
| 439 | let before = guest.shared.state.lock().unwrap().relay.clone().unwrap(); |
| 440 | assert!(!Arc::ptr_eq(&before_timeout, &before)); |
| 441 | for edit in [41_u64, 42] { |
| 442 | line.send(kind::EDITS, &edit).unwrap(); |
| 443 | } |
| 444 | line.hang_up("left"); |
| 445 | let mut delivered = Vec::new(); |
| 446 | loop { |
| 447 | let (kind, body) = frames.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 448 | match kind { |
| 449 | kind::EDITS => delivered.push(minicbor::decode::<u64>(&body).unwrap()), |
| 450 | kind::BYE => break, |
| 451 | _ => {} |
| 452 | } |
| 453 | } |
| 454 | assert_eq!(delivered, [41, 42]); |
| 455 | guest_left.recv_timeout(Duration::from_secs(10)).unwrap(); |
| 456 | host.set_presence(caret(43)); |
| 457 | until(&guest, |peers| { |
| 458 | peers |
| 459 | .first() |
| 460 | .is_some_and(|peer| peer.presence == Some(caret(43))) |
| 461 | }); |
| 462 | let state = guest.shared.state.lock().unwrap(); |
| 463 | assert!(Arc::ptr_eq(&before, state.relay.as_ref().unwrap())); |
| 464 | drop(state); |
| 465 | assert!(matches!( |
| 466 | host_met.try_recv(), |
| 467 | Err(mpsc::TryRecvError::Empty) |
| 468 | )); |
| 469 | assert!(matches!( |
| 470 | guest_met.try_recv(), |
| 471 | Err(mpsc::TryRecvError::Empty) |
| 472 | )); |
| 473 | assert!(matches!( |
| 474 | guest_frames.try_recv(), |
| 475 | Err(mpsc::TryRecvError::Empty) |
| 476 | )); |
| 477 | drop(guest); |
| 478 | drop(host); |
| 479 | proxy.join().unwrap(); |
| 480 | } |
| 481 | |
| 295 | 482 | /// The end sharing a code asks the relay to number it; the other types the whole code. One |
| 296 | 483 | /// with the wrong words never meets, and the relay, told so by the end sharing, burns the |
| 297 | 484 | /// code once too many have tried. |