Files
figcraft/crates/engine/src/live_peer_cap_tests.rs
T
BFlat f1eaf3fcb4 M8: cap the people a client keeps track of and the deleted ids a session remembers (#1622)
* M8: cap the people a client keeps track of and the deleted ids a session remembers (#1121)

A hostile host or server could grow a client's presence, seen-peers and who-is-here lists without bound, and a session's deleted-id set only grew. Clients keep at most MAX_PEERS (4,096) others, leaving newcomers out with a warning; max_people is at most that; a session remembers at most 262,144 deleted ids, the lowest forgotten first.

* M8: a max_people above what clients track is lowered with a warning, not refused (#1121)

Refusing it would stop a server whose existing config says more from starting after an upgrade. Config::people() lowers max_people to MAX_PEERS for the core's limits, and the startup warnings name the configured and the used value.

* M8: the live welcome's handling in its own function (Sonar S3776)
2026-10-30 12:33:29 +07:00

37 lines
1.7 KiB
Rust

//! A hostile server can't grow a client's list of people without bound (#1121): past
//! [`MAX_PEERS`], someone new is left out (their presence and chat with them), in a welcome or one
//! join at a time; someone leaving makes room again.
use serde_json::json;
use crate::live::{Event, MAX_PEERS, Peer, Presence, Role};
use crate::live_tests::{info, peer, push, session};
fn many(from: u32, n: usize) -> Vec<Peer> {
(from..).take(n).map(|id| peer(id, "Someone")).collect()
}
#[test]
fn a_flood_of_joins_keeps_at_most_the_cap() {
let (mut s, script) = session();
s.execute("collab.join", &json!({"link": "figcraft://join?host=a:1&file=f&token=t"})).unwrap();
push(&script, vec![Event::Welcome { info: info(Role::Guest, 1), peers: many(2, MAX_PEERS + 100) }]);
s.live_poll();
assert_eq!(s.live().peers.len(), MAX_PEERS, "a welcome listing too many");
let first_left_out = 2 + MAX_PEERS as u32;
let joins = many(first_left_out, 1_000).into_iter().map(Event::PeerJoined).collect();
push(&script, joins);
s.live_poll();
assert_eq!(s.live().peers.len(), MAX_PEERS, "joins past the cap");
// Presence of someone left out isn't kept either.
push(&script, vec![Event::Presence(first_left_out, Presence::default())]);
s.live_poll();
assert!(!s.live().presence.contains_key(&first_left_out));
// Someone already here may still be renamed; someone leaving makes room.
push(&script, vec![Event::PeerJoined(peer(2, "Renamed")), Event::PeerLeft(3), Event::PeerJoined(peer(first_left_out, "Late"))]);
s.live_poll();
assert_eq!(s.live().peers.len(), MAX_PEERS);
assert_eq!(s.live().peers[&2].name, "Renamed");
assert!(s.live().peers.contains_key(&first_left_out) && !s.live().peers.contains_key(&3));
}