| ... | ... | @@ -1,8 +1,9 @@ |
| 1 | | use super::super::{GONE, OPENING, Pipe, Shared, State}; |
| 1 | use super::super::super::{GONE, OPENING, Pipe, Shared, State}; |
| 2 | 2 | use super::*; |
| 3 | 3 | use std::{ |
| 4 | 4 | io::{Read, Write}, |
| 5 | 5 | net::{Shutdown, TcpListener, TcpStream}, |
| 6 | sync::atomic::AtomicUsize, |
| 6 | 7 | }; |
| 7 | 8 | |
| 8 | 9 | struct Barrier { |
| ... | ... | @@ -144,7 +145,7 @@ impl Handover { |
| 144 | 145 | } |
| 145 | 146 | } |
| 146 | 147 | |
| 147 | | fn open(&mut self, host: &Hello, direct: bool) -> (TcpStream, wire::Sealer) { |
| 148 | fn open(&mut self, host: &Hello, direct: bool) -> (TcpStream, wire::Sealer, wire::Sealer) { |
| 148 | 149 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); |
| 149 | 150 | let mut remote = |
| 150 | 151 | TcpStream::connect_timeout(&listener.local_addr().unwrap(), OPENING).unwrap(); |
| ... | ... | @@ -180,7 +181,7 @@ impl Handover { |
| 180 | 181 | let (message, body) = receive.receive(&mut remote).unwrap(); |
| 181 | 182 | assert_eq!(message, kind::HELLO); |
| 182 | 183 | assert_eq!(minicbor::decode::<Hello>(&body).unwrap(), self.shared.me); |
| 183 | | (remote, send) |
| 184 | (remote, send, receive) |
| 184 | 185 | } |
| 185 | 186 | |
| 186 | 187 | fn heard(&self) { |
| ... | ... | @@ -198,25 +199,17 @@ impl Drop for Handover { |
| 198 | 199 | pipe.shutdown(); |
| 199 | 200 | } |
| 200 | 201 | self.shared.state.lock().unwrap().peers.clear(); |
| 201 | | let deadline = std::time::Instant::now() + OPENING; |
| 202 | | for (_, reader) in &self.readers { |
| 203 | | while !reader.is_finished() && std::time::Instant::now() < deadline { |
| 204 | | thread::yield_now(); |
| 205 | | } |
| 206 | | } |
| 207 | | let readers_done = self.readers.iter().all(|(_, reader)| reader.is_finished()); |
| 202 | let readers_done = self |
| 203 | .readers |
| 204 | .iter() |
| 205 | .all(|(done, _)| done.recv_timeout(OPENING) != Err(mpsc::RecvTimeoutError::Timeout)); |
| 208 | 206 | self.guest.host.lock().unwrap().take(); |
| 209 | 207 | let count = self.pipes.len(); |
| 210 | 208 | self.pipes.clear(); |
| 211 | | let retired = (0..count).all(|_| self.retired.1.recv_timeout(OPENING).is_ok()); |
| 212 | | let mut joined = readers_done && retired; |
| 213 | | if readers_done { |
| 214 | | for (_, reader) in self.readers.drain(..) { |
| 215 | | joined &= reader.join().is_ok(); |
| 216 | | } |
| 217 | | } |
| 209 | let joined = readers_done |
| 210 | && (0..count).all(|_| self.retired.1.recv_timeout(OPENING).is_ok()) |
| 211 | && (self.readers.drain(..)).all(|(_, reader)| reader.join().is_ok()); |
| 218 | 212 | if !thread::panicking() { |
| 219 | | println!("HANDOVER_READERS_JOINED_AND_PIPE_OWNERS_RETIRED={joined};PIPES={count}"); |
| 220 | 213 | assert!( |
| 221 | 214 | joined, |
| 222 | 215 | "handover reader/writer owners did not retire and join" |
| ... | ... | @@ -230,7 +223,7 @@ fn handover_stale_frame_cannot_end_promoted_guest() { |
| 230 | 223 | let mut fixture = Handover::new(None); |
| 231 | 224 | let mut host = Hello::new("Old host route".into(), None).unwrap(); |
| 232 | 225 | host.serves = Some(fixture.guest.share); |
| 233 | | let (mut old, mut send) = fixture.open(&host, false); |
| 226 | let (mut old, mut send, _) = fixture.open(&host, false); |
| 234 | 227 | fixture.heard(); |
| 235 | 228 | let (gate, copied, resume) = barrier(); |
| 236 | 229 | fixture.releases.push(resume.clone()); |
| ... | ... | @@ -245,7 +238,7 @@ fn handover_stale_frame_cannot_end_promoted_guest() { |
| 245 | 238 | .unwrap(); |
| 246 | 239 | copied.recv_timeout(OPENING).unwrap(); |
| 247 | 240 | host.name = "Promoted host route".into(); |
| 248 | | let (_new, _) = fixture.open(&host, true); |
| 241 | let (_new, _, _) = fixture.open(&host, true); |
| 249 | 242 | fixture.heard(); |
| 250 | 243 | assert!( |
| 251 | 244 | fixture.shared.state.lock().unwrap().peers[&host.peer] |
| ... | ... | @@ -257,10 +250,42 @@ fn handover_stale_frame_cannot_end_promoted_guest() { |
| 257 | 250 | fixture.readers[0].0.recv_timeout(OPENING).unwrap(); |
| 258 | 251 | let ended = *fixture.guest.ended.lock().unwrap(); |
| 259 | 252 | drop(fixture); |
| 260 | | println!("HANDOVER_STALE_FRAME_ENDED={ended:?}"); |
| 261 | 253 | assert_eq!(ended, None, "stale frame ended the promoted guest"); |
| 262 | 254 | } |
| 263 | 255 | |
| 256 | #[test] |
| 257 | fn handover_reply_from_retired_route_reaches_its_waiter() { |
| 258 | let mut fixture = Handover::new(None); |
| 259 | let mut host = Hello::new("Old host route".into(), None).unwrap(); |
| 260 | host.serves = Some(fixture.guest.share); |
| 261 | let (mut old, mut send, _) = fixture.open(&host, false); |
| 262 | fixture.heard(); |
| 263 | let (answer, waiting) = crate::task::response::<Reply>(); |
| 264 | fixture.guest.pending.lock().unwrap().insert(75, answer); |
| 265 | let (gate, copied, resume) = barrier(); |
| 266 | fixture.releases.push(resume.clone()); |
| 267 | *fixture.pipes[0].body.lock().unwrap() = Some(gate); |
| 268 | let reply = Reply { |
| 269 | id: 75, |
| 270 | length: Some(12), |
| 271 | ..Reply::default() |
| 272 | }; |
| 273 | send.send(&mut old, kind::REPLY, &reply).unwrap(); |
| 274 | copied.recv_timeout(OPENING).unwrap(); |
| 275 | host.name = "Promoted host route".into(); |
| 276 | let (_new, _, _) = fixture.open(&host, true); |
| 277 | fixture.heard(); |
| 278 | resume.send(()).unwrap(); |
| 279 | fixture.readers[0].0.recv_timeout(OPENING).unwrap(); |
| 280 | let answered = waiting.try_recv().ok(); |
| 281 | drop(fixture); |
| 282 | assert_eq!( |
| 283 | answered, |
| 284 | Some(reply), |
| 285 | "a reply read off a retired route missed its waiter" |
| 286 | ); |
| 287 | } |
| 288 | |
| 264 | 289 | #[test] |
| 265 | 290 | fn handover_stale_met_cannot_replace_promoted_guest_line() { |
| 266 | 291 | let (gate, delayed, resume) = barrier(); |
| ... | ... | @@ -268,28 +293,17 @@ fn handover_stale_met_cannot_replace_promoted_guest_line() { |
| 268 | 293 | fixture.releases.push(resume.clone()); |
| 269 | 294 | let mut host = Hello::new("Old host route".into(), None).unwrap(); |
| 270 | 295 | host.serves = Some(fixture.guest.share); |
| 271 | | let (_old, _) = fixture.open(&host, false); |
| 296 | let (_old, _, _) = fixture.open(&host, false); |
| 272 | 297 | delayed.recv_timeout(OPENING).unwrap(); |
| 273 | 298 | host.name = "Promoted host route".into(); |
| 274 | | let (_new, _) = fixture.open(&host, true); |
| 275 | | let overlap = match fixture.observed.recv_timeout(OPENING) { |
| 276 | | Ok(()) => { |
| 277 | | assert_eq!( |
| 278 | | fixture.guest.host.lock().unwrap().as_ref().unwrap().0.name, |
| 279 | | host.name |
| 280 | | ); |
| 281 | | resume.send(()).unwrap(); |
| 282 | | fixture.heard(); |
| 283 | | true |
| 284 | | } |
| 285 | | Err(mpsc::RecvTimeoutError::Timeout) => { |
| 286 | | resume.send(()).unwrap(); |
| 287 | | fixture.heard(); |
| 288 | | fixture.heard(); |
| 289 | | false |
| 290 | | } |
| 291 | | Err(error) => panic!("handover event channel closed: {error}"), |
| 292 | | }; |
| 299 | let (_new, _, _) = fixture.open(&host, true); |
| 300 | fixture.heard(); |
| 301 | assert_eq!( |
| 302 | fixture.guest.host.lock().unwrap().as_ref().unwrap().0.name, |
| 303 | host.name |
| 304 | ); |
| 305 | resume.send(()).unwrap(); |
| 306 | fixture.heard(); |
| 293 | 307 | assert_eq!( |
| 294 | 308 | fixture.shared.state.lock().unwrap().peers[&host.peer] |
| 295 | 309 | .peer |
| ... | ... | @@ -308,7 +322,6 @@ fn handover_stale_met_cannot_replace_promoted_guest_line() { |
| 308 | 322 | .name |
| 309 | 323 | .clone(); |
| 310 | 324 | drop(fixture); |
| 311 | | println!("HANDOVER_MET_PROMOTION_OVERLAP={overlap}"); |
| 312 | 325 | assert_eq!( |
| 313 | 326 | selected, host.name, |
| 314 | 327 | "stale Met replaced the promoted guest Line" |
| ... | ... | @@ -364,12 +377,12 @@ fn handover_old_left_preserves_promoted_host_and_pending_reply() { |
| 364 | 377 | }); |
| 365 | 378 | let mut host = Hello::new("Old host route".into(), None).unwrap(); |
| 366 | 379 | host.serves = Some(fixture.guest.share); |
| 367 | | let (old, _) = fixture.open(&host, false); |
| 380 | let (old, _, _) = fixture.open(&host, false); |
| 368 | 381 | observed(&events, kind::HELLO); |
| 369 | 382 | old.shutdown(Shutdown::Both).unwrap(); |
| 370 | 383 | removed.recv_timeout(OPENING).unwrap(); |
| 371 | 384 | host.name = "Promoted host route".into(); |
| 372 | | let (_new, _) = fixture.open(&host, true); |
| 385 | let (_new, _, _) = fixture.open(&host, true); |
| 373 | 386 | observed(&events, kind::HELLO); |
| 374 | 387 | let (answer, waiting) = crate::task::response::<Reply>(); |
| 375 | 388 | fixture.guest.pending.lock().unwrap().insert(73, answer); |
| ... | ... | @@ -396,7 +409,7 @@ fn handover_selected_loss_clears_host_and_disconnects_pending_reply() { |
| 396 | 409 | let mut fixture = Handover::new(None); |
| 397 | 410 | let mut host = Hello::new("Selected host".into(), None).unwrap(); |
| 398 | 411 | host.serves = Some(fixture.guest.share); |
| 399 | | let (remote, _) = fixture.open(&host, true); |
| 412 | let (remote, _, _) = fixture.open(&host, true); |
| 400 | 413 | fixture.heard(); |
| 401 | 414 | let (answer, waiting) = crate::task::response::<Reply>(); |
| 402 | 415 | fixture.guest.pending.lock().unwrap().insert(74, answer); |
| ... | ... | @@ -434,34 +447,17 @@ fn served() -> (tempfile::TempDir, Arc<Served>) { |
| 434 | 447 | #[test] |
| 435 | 448 | fn handover_copied_host_request_cannot_enter_promoted_queue() { |
| 436 | 449 | let (_directory, served) = served(); |
| 437 | | let queues = Arc::new(Mutex::new(Vec::new())); |
| 438 | 450 | let mut fixture = Handover::new(None); |
| 439 | 451 | let serving = Arc::clone(&served); |
| 440 | | let received = Arc::clone(&queues); |
| 441 | 452 | let events = fixture.intercept(move |source, event| match event { |
| 442 | | Event::Met(hello, line) => { |
| 443 | | let (queue, waiting) = mpsc::sync_channel(4); |
| 444 | | let retired = serving.guests.lock().unwrap().insert( |
| 445 | | hello.peer, |
| 446 | | Admitted { |
| 447 | | hello: Arc::clone(hello), |
| 448 | | line: (*line).clone(), |
| 449 | | queue, |
| 450 | | starts: BURST, |
| 451 | | counted: Instant::now(), |
| 452 | | held: Vec::new(), |
| 453 | | }, |
| 454 | | ); |
| 455 | | drop(retired); |
| 456 | | received.lock().unwrap().push(waiting); |
| 457 | | } |
| 453 | Event::Met(hello, line) => assert!(serving.admit(source, hello, line)), |
| 458 | 454 | Event::Frame { from, kind, body } => serving.queue(source, from, *kind, body), |
| 459 | 455 | Event::Left(hello) => serving.forget(&hello.peer, Some((source, hello))), |
| 460 | 456 | _ => {} |
| 461 | 457 | }); |
| 462 | 458 | let mut host = Hello::new("Old route".into(), None).unwrap(); |
| 463 | 459 | host.serves = Some(fixture.guest.share); |
| 464 | | let (mut old, mut send) = fixture.open(&host, false); |
| 460 | let (mut old, mut send, _) = fixture.open(&host, false); |
| 465 | 461 | observed(&events, kind::HELLO); |
| 466 | 462 | let (gate, copied, resume) = barrier(); |
| 467 | 463 | fixture.releases.push(resume.clone()); |
| ... | ... | @@ -471,6 +467,8 @@ fn handover_copied_host_request_cannot_enter_promoted_queue() { |
| 471 | 467 | kind::PUT, |
| 472 | 468 | &Request { |
| 473 | 469 | id: 80, |
| 470 | handle: Some(1), |
| 471 | offset: Some(0), |
| 474 | 472 | bytes: Some(vec![1, 2, 3]), |
| 475 | 473 | ..Request::default() |
| 476 | 474 | }, |
| ... | ... | @@ -478,31 +476,41 @@ fn handover_copied_host_request_cannot_enter_promoted_queue() { |
| 478 | 476 | .unwrap(); |
| 479 | 477 | copied.recv_timeout(OPENING).unwrap(); |
| 480 | 478 | host.name = "Promoted route".into(); |
| 481 | | let (mut new, mut send) = fixture.open(&host, true); |
| 479 | let (mut new, mut send, mut receive) = fixture.open(&host, true); |
| 482 | 480 | observed(&events, kind::HELLO); |
| 483 | 481 | resume.send(()).unwrap(); |
| 484 | 482 | fixture.readers[0].0.recv_timeout(OPENING).unwrap(); |
| 485 | | let no_stale_request = matches!( |
| 486 | | queues.lock().unwrap()[1].try_recv(), |
| 487 | | Err(mpsc::TryRecvError::Empty) |
| 488 | | ); |
| 489 | 483 | send.send( |
| 490 | 484 | &mut new, |
| 491 | 485 | kind::PUT, |
| 492 | 486 | &Request { |
| 493 | 487 | id: 81, |
| 488 | handle: Some(2), |
| 489 | offset: Some(0), |
| 494 | 490 | bytes: Some(vec![4, 5, 6]), |
| 495 | 491 | ..Request::default() |
| 496 | 492 | }, |
| 497 | 493 | ) |
| 498 | 494 | .unwrap(); |
| 499 | | let (kind, body) = queues.lock().unwrap()[1].recv_timeout(OPENING).unwrap(); |
| 500 | | let accepted = kind == kind::PUT && minicbor::decode::<Request>(&body).unwrap().id == 81; |
| 495 | let reply = loop { |
| 496 | let (kind, body) = receive.receive(&mut new).unwrap(); |
| 497 | if kind == kind::REPLY { |
| 498 | break minicbor::decode::<Reply>(&body).unwrap(); |
| 499 | } |
| 500 | }; |
| 501 | let puts = served |
| 502 | .puts |
| 503 | .lock() |
| 504 | .unwrap() |
| 505 | .keys() |
| 506 | .copied() |
| 507 | .collect::<Vec<_>>(); |
| 501 | 508 | served.guests.lock().unwrap().clear(); |
| 502 | 509 | drop(fixture); |
| 503 | | assert!( |
| 504 | | no_stale_request && accepted, |
| 505 | | "retired request entered the promoted queue or current request was lost" |
| 510 | assert_eq!( |
| 511 | (reply.id, reply.failure, puts), |
| 512 | (81, None, vec![(host.peer, 2)]), |
| 513 | "retired request reached the promoted guest or current request was lost" |
| 506 | 514 | ); |
| 507 | 515 | } |
| 508 | 516 | |
| ... | ... | @@ -514,25 +522,8 @@ fn handover_old_left_preserves_promoted_host_uploads_and_snapshots() { |
| 514 | 522 | fixture.releases.push(resume.clone()); |
| 515 | 523 | let gate = Mutex::new(Some(gate)); |
| 516 | 524 | let serving = Arc::clone(&served); |
| 517 | | let waiting = Arc::new(Mutex::new(Vec::new())); |
| 518 | | let keep_queues = Arc::clone(&waiting); |
| 519 | 525 | let events = fixture.intercept(move |source, event| match event { |
| 520 | | Event::Met(hello, line) => { |
| 521 | | let (queue, received) = mpsc::sync_channel(1); |
| 522 | | let retired = serving.guests.lock().unwrap().insert( |
| 523 | | hello.peer, |
| 524 | | Admitted { |
| 525 | | hello: Arc::clone(hello), |
| 526 | | line: (*line).clone(), |
| 527 | | queue, |
| 528 | | starts: BURST, |
| 529 | | counted: Instant::now(), |
| 530 | | held: Vec::new(), |
| 531 | | }, |
| 532 | | ); |
| 533 | | drop(retired); |
| 534 | | keep_queues.lock().unwrap().push(received); |
| 535 | | } |
| 526 | Event::Met(hello, line) => assert!(serving.admit(source, hello, line)), |
| 536 | 527 | Event::Left(hello) => { |
| 537 | 528 | if let Some(gate) = gate.lock().unwrap().take() { |
| 538 | 529 | gate.wait().unwrap(); |
| ... | ... | @@ -543,12 +534,12 @@ fn handover_old_left_preserves_promoted_host_uploads_and_snapshots() { |
| 543 | 534 | }); |
| 544 | 535 | let mut host = Hello::new("Old route".into(), None).unwrap(); |
| 545 | 536 | host.serves = Some(fixture.guest.share); |
| 546 | | let (old, _) = fixture.open(&host, false); |
| 537 | let (old, _, _) = fixture.open(&host, false); |
| 547 | 538 | observed(&events, kind::HELLO); |
| 548 | 539 | old.shutdown(Shutdown::Both).unwrap(); |
| 549 | 540 | removed.recv_timeout(OPENING).unwrap(); |
| 550 | 541 | host.name = "Promoted route".into(); |
| 551 | | let (_new, _) = fixture.open(&host, true); |
| 542 | let (_new, _, _) = fixture.open(&host, true); |
| 552 | 543 | observed(&events, kind::HELLO); |
| 553 | 544 | served |
| 554 | 545 | .puts |
| ... | ... | @@ -588,18 +579,10 @@ fn handover_deferred_presence_cannot_replace_promoted_host_room() { |
| 588 | 579 | let mut fixture = Handover::new(None); |
| 589 | 580 | let mut host = Hello::new("Old host route".into(), None).unwrap(); |
| 590 | 581 | host.serves = Some(fixture.guest.share); |
| 591 | | let (_old, _) = fixture.open(&host, false); |
| 582 | let (_old, _, _) = fixture.open(&host, false); |
| 592 | 583 | fixture.heard(); |
| 593 | 584 | let from = Arc::clone(&fixture.guest.host.lock().unwrap().as_ref().unwrap().0); |
| 594 | 585 | let secret = [61; 16]; |
| 595 | | let live = Live::start( |
| 596 | | fixture.guest.me.clone(), |
| 597 | | &Room::Notebook(secret), |
| 598 | | None, |
| 599 | | None, |
| 600 | | |_| {}, |
| 601 | | ) |
| 602 | | .unwrap(); |
| 603 | 586 | let guest = Arc::clone(&fixture.guest); |
| 604 | 587 | let source = Arc::clone(&fixture.shared); |
| 605 | 588 | let here_owner = Arc::clone(&fixture.guest); |
| ... | ... | @@ -608,14 +591,14 @@ fn handover_deferred_presence_cannot_replace_promoted_host_room() { |
| 608 | 591 | let (finished, done) = mpsc::channel(); |
| 609 | 592 | let installer = thread::spawn(move || { |
| 610 | 593 | entered.send(()).unwrap(); |
| 611 | | guest.install_presence(&source, &from, secret, live); |
| 594 | guest.join_presence(&source, &from, secret); |
| 612 | 595 | let _ = finished.send(()); |
| 613 | 596 | true |
| 614 | 597 | }); |
| 615 | 598 | fixture.readers.push((done, installer)); |
| 616 | 599 | started.recv_timeout(OPENING).unwrap(); |
| 617 | 600 | host.name = "Promoted host route".into(); |
| 618 | | let (_new, _) = fixture.open(&host, true); |
| 601 | let (_new, _, _) = fixture.open(&host, true); |
| 619 | 602 | fixture.heard(); |
| 620 | 603 | drop(here); |
| 621 | 604 | fixture.readers[1].0.recv_timeout(OPENING).unwrap(); |
| ... | ... | @@ -639,18 +622,10 @@ fn handover_presence_install_seeds_latest_here() { |
| 639 | 622 | let mut fixture = Handover::new(None); |
| 640 | 623 | let mut host = Hello::new("Current host".into(), None).unwrap(); |
| 641 | 624 | host.serves = Some(fixture.guest.share); |
| 642 | | let (_remote, _) = fixture.open(&host, true); |
| 625 | let (_remote, _, _) = fixture.open(&host, true); |
| 643 | 626 | fixture.heard(); |
| 644 | 627 | let from = Arc::clone(&fixture.guest.host.lock().unwrap().as_ref().unwrap().0); |
| 645 | 628 | let secret = [62; 16]; |
| 646 | | let live = Live::start( |
| 647 | | fixture.guest.me.clone(), |
| 648 | | &Room::Notebook(secret), |
| 649 | | None, |
| 650 | | None, |
| 651 | | |_| {}, |
| 652 | | ) |
| 653 | | .unwrap(); |
| 654 | 629 | let guest = Arc::clone(&fixture.guest); |
| 655 | 630 | let source = Arc::clone(&fixture.shared); |
| 656 | 631 | let mut here = fixture.guest.here.lock().unwrap(); |
| ... | ... | @@ -662,7 +637,7 @@ fn handover_presence_install_seeds_latest_here() { |
| 662 | 637 | let (finished, done) = mpsc::channel(); |
| 663 | 638 | let installer = thread::spawn(move || { |
| 664 | 639 | entered.send(()).unwrap(); |
| 665 | | guest.install_presence(&source, &from, secret, live); |
| 640 | guest.join_presence(&source, &from, secret); |
| 666 | 641 | let _ = finished.send(()); |
| 667 | 642 | true |
| 668 | 643 | }); |
| ... | ... | @@ -699,7 +674,7 @@ pub(super) fn promoted_routes<T>( |
| 699 | 674 | shared.secret = room.secret(); |
| 700 | 675 | shared.room = room; |
| 701 | 676 | let host = Hello::new("Pairing device".into(), None).unwrap(); |
| 702 | | let (_old, _) = fixture.open(&host, false); |
| 677 | let (_old, _, _) = fixture.open(&host, false); |
| 703 | 678 | fixture.heard(); |
| 704 | 679 | let (old, old_line) = { |
| 705 | 680 | let state = fixture.shared.state.lock().unwrap(); |
| ... | ... | @@ -707,7 +682,7 @@ pub(super) fn promoted_routes<T>( |
| 707 | 682 | (Arc::clone(&link.peer.hello), link.line.clone()) |
| 708 | 683 | }; |
| 709 | 684 | before(&fixture.shared, &old, &old_line); |
| 710 | | let (_new, _) = fixture.open(&host, true); |
| 685 | let (_new, _, _) = fixture.open(&host, true); |
| 711 | 686 | fixture.heard(); |
| 712 | 687 | let (new, new_line) = { |
| 713 | 688 | let state = fixture.shared.state.lock().unwrap(); |
| ... | ... | @@ -719,3 +694,147 @@ pub(super) fn promoted_routes<T>( |
| 719 | 694 | drop(fixture); |
| 720 | 695 | result |
| 721 | 696 | } |
| 697 | |
| 698 | #[test] |
| 699 | fn handover_pair_pending_keeps_promoted_identity_and_rejects_retired_allow() { |
| 700 | let directory = tempfile::tempdir().unwrap(); |
| 701 | let folder = directory.path().join("notebook"); |
| 702 | std::fs::create_dir(&folder).unwrap(); |
| 703 | let sharing = Sharing { |
| 704 | approve: true, |
| 705 | code: super::super::super::code::format(701, "ABCDEF").unwrap(), |
| 706 | ..Sharing::new("").unwrap() |
| 707 | }; |
| 708 | let room = Room::share(&sharing.code, &sharing.password); |
| 709 | let host = Host::start( |
| 710 | crate::session::Notebook::open(&folder, directory.path().join("cache")) |
| 711 | .unwrap() |
| 712 | .into_storage(), |
| 713 | Hello::new("Notebook owner".into(), None).unwrap(), |
| 714 | sharing, |
| 715 | "Fixture", |
| 716 | None, |
| 717 | None, |
| 718 | || {}, |
| 719 | |_| Ok(()), |
| 720 | ) |
| 721 | .unwrap(); |
| 722 | let callback = Arc::clone(&host.pairing.lock().unwrap().as_ref().unwrap().shared); |
| 723 | let valid = promoted_routes( |
| 724 | room, |
| 725 | |source, old, old_line| { |
| 726 | (callback.events)(source, Event::Met(old, old_line)); |
| 727 | }, |
| 728 | |source, old, old_line, new, new_line| { |
| 729 | host.allow(&old.peer).unwrap(); |
| 730 | let no_grant = host.sharing().members.is_empty(); |
| 731 | (callback.events)(source, Event::Met(new, new_line)); |
| 732 | (callback.events)(source, Event::Met(old, old_line)); |
| 733 | (callback.events)(source, Event::Left(old)); |
| 734 | let kept = host |
| 735 | .requests() |
| 736 | .first() |
| 737 | .is_some_and(|request| Arc::ptr_eq(request, new)); |
| 738 | host.stop(); |
| 739 | kept && no_grant |
| 740 | }, |
| 741 | ); |
| 742 | assert!( |
| 743 | valid, |
| 744 | "pairing admitted a retired route or retired Left removed the promoted request" |
| 745 | ); |
| 746 | } |
| 747 | |
| 748 | #[test] |
| 749 | fn handover_pair_retired_met_cannot_persist_another_credential() { |
| 750 | let directory = tempfile::tempdir().unwrap(); |
| 751 | let folder = directory.path().join("notebook"); |
| 752 | std::fs::create_dir(&folder).unwrap(); |
| 753 | let sharing = Sharing { |
| 754 | approve: false, |
| 755 | code: super::super::super::code::format(702, "ABCDEF").unwrap(), |
| 756 | ..Sharing::new("").unwrap() |
| 757 | }; |
| 758 | let room = Room::share(&sharing.code, &sharing.password); |
| 759 | let kept = Arc::new(AtomicUsize::new(0)); |
| 760 | let count = Arc::clone(&kept); |
| 761 | let host = Host::start( |
| 762 | crate::session::Notebook::open(&folder, directory.path().join("cache")) |
| 763 | .unwrap() |
| 764 | .into_storage(), |
| 765 | Hello::new("Notebook owner".into(), None).unwrap(), |
| 766 | sharing, |
| 767 | "Fixture", |
| 768 | None, |
| 769 | None, |
| 770 | || {}, |
| 771 | move |_| { |
| 772 | count.fetch_add(1, Ordering::SeqCst); |
| 773 | Ok(()) |
| 774 | }, |
| 775 | ) |
| 776 | .unwrap(); |
| 777 | let callback = Arc::clone(&host.pairing.lock().unwrap().as_ref().unwrap().shared); |
| 778 | let valid = promoted_routes( |
| 779 | room, |
| 780 | |_, _, _| {}, |
| 781 | |source, old, old_line, new, new_line| { |
| 782 | (callback.events)(source, Event::Met(new, new_line)); |
| 783 | let after_current = kept.load(Ordering::SeqCst); |
| 784 | (callback.events)(source, Event::Met(old, old_line)); |
| 785 | let refused = kept.load(Ordering::SeqCst) == after_current |
| 786 | && after_current == 2 |
| 787 | && host.sharing().members.len() == 1; |
| 788 | host.stop(); |
| 789 | refused |
| 790 | }, |
| 791 | ); |
| 792 | assert!(valid, "retired pairing Met persisted another credential"); |
| 793 | } |
| 794 | |
| 795 | #[test] |
| 796 | fn handover_retired_membership_rejects_deferred_access_met() { |
| 797 | let directory = tempfile::tempdir().unwrap(); |
| 798 | let folder = directory.path().join("notebook"); |
| 799 | std::fs::create_dir(&folder).unwrap(); |
| 800 | let secret = [71; 16]; |
| 801 | let sharing = Sharing { |
| 802 | code: super::super::super::code::format(703, "ABCDEF").unwrap(), |
| 803 | members: vec![Device { |
| 804 | secret, |
| 805 | name: "Retired device".into(), |
| 806 | device: None, |
| 807 | }], |
| 808 | ..Sharing::new("").unwrap() |
| 809 | }; |
| 810 | let host = Host::start( |
| 811 | crate::session::Notebook::open(&folder, directory.path().join("cache")) |
| 812 | .unwrap() |
| 813 | .into_storage(), |
| 814 | Hello::new("Notebook owner".into(), None).unwrap(), |
| 815 | sharing, |
| 816 | "Fixture", |
| 817 | None, |
| 818 | None, |
| 819 | || {}, |
| 820 | |_| Ok(()), |
| 821 | ) |
| 822 | .unwrap(); |
| 823 | let callback = Arc::clone(&host.members.access.lock().unwrap()[&secret].shared); |
| 824 | let refused = promoted_routes( |
| 825 | Room::Notebook(secret), |
| 826 | |_, _, _| {}, |
| 827 | |source, _, _, current, line| { |
| 828 | host.remove(&secret).unwrap(); |
| 829 | (callback.events)(source, Event::Met(current, line)); |
| 830 | let refused = host.members.served.guests.lock().unwrap().is_empty() |
| 831 | && host.sharing().members.is_empty(); |
| 832 | host.stop(); |
| 833 | refused |
| 834 | }, |
| 835 | ); |
| 836 | assert!( |
| 837 | refused, |
| 838 | "deferred access callback readmitted a removed device" |
| 839 | ); |
| 840 | } |