Files
peerspeak/src/bin/test_net.rs
T
molluskandClaude Opus 4.8 961705ffa9 feat(game): broadcast game presence (GOSSIP_PROTO 3) + per-game background
Steps 5-6 of game detection. BREAKING wire change — bump everyone.

Wire (step 5):
- PeerState.game: Option<String> (display label only — never appid/source).
- SelfPresence.game + to_state carry it (single self-state builder).
- GOSSIP_PROTO 2->3, GOSSIP_SIG_DOMAIN v3, version comment bumped together;
  Cargo MINOR 0.3.0 -> 0.4.0 per VERSIONING.md. v2/v3 isolate into
  different topics + signature domains, so a coordinated redeploy is
  required (same as the W4 avatar bump).
- Gossip ingest sanitizes incoming game via sanitize_game_label (bidi/
  control strip, 64-char/256-byte cap); empty -> None.
- Bonus security fix (Codex find): reject inbound gossip frames over a
  128KB cap BEFORE serde_json::from_slice — a legit Announce with a full
  48KB avatar is ~49KB, so this bounds allocation abuse with headroom.

Core wiring:
- Spawns the detector at startup; consumes its watch channel in the main
  select. Detection runs continuously (for the local background); the
  broadcast is gated by game_presence_enabled (opt-in, default OFF).
  New commands: SetGamePresenceEnabled (immediate publish/clear, D8),
  SetGameOverride, SetGameProcessMap. New event: GameChanged.
- game_presence_label sanitizes the outgoing label too.

Background switch (step 6):
- GUI handles GameChanged: stores current_game, swaps background to the
  per-game override (config.game_backgrounds[id]) or falls back to the
  W16 default; reuses the existing cached-handle path (no redraw flicker).

397 lib tests (all green), clippy --all-targets clean, full binary builds.
Remaining: step 7 UI (opt-in toggle, roster 'Playing' text, manual
override control, Settings game-backgrounds + process-map editors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:31:09 -04:00

110 lines
3.4 KiB
Rust

use peerspeak::network::{
gossip::IrohGossipState,
RoomState, PeerState,
};
use iroh::{Endpoint, endpoint::presets};
use iroh_gossip::net::Gossip;
use tokio::time::{self, Duration};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Starting network loopback test...");
// 1. Node A (Host) Setup
let lookup_a = iroh::address_lookup::memory::MemoryLookup::new();
let secret_a = iroh::SecretKey::generate();
let endpoint_a = Endpoint::builder(presets::N0)
.secret_key(secret_a.clone())
.address_lookup(lookup_a.clone())
.bind()
.await?;
endpoint_a.online().await;
println!("Node A online. ID: {}", endpoint_a.id());
let gossip_a = Gossip::builder().spawn(endpoint_a.clone());
let _router_a = iroh::protocol::Router::builder(endpoint_a.clone())
.accept(iroh_gossip::net::GOSSIP_ALPN, gossip_a.clone())
.spawn();
let room_a = IrohGossipState::new(endpoint_a.clone(), gossip_a.clone(), lookup_a.clone(), secret_a);
// 2. Node B (Client) Setup
let lookup_b = iroh::address_lookup::memory::MemoryLookup::new();
let secret_b = iroh::SecretKey::generate();
let endpoint_b = Endpoint::builder(presets::N0)
.secret_key(secret_b.clone())
.address_lookup(lookup_b.clone())
.bind()
.await?;
endpoint_b.online().await;
println!("Node B online. ID: {}", endpoint_b.id());
let gossip_b = Gossip::builder().spawn(endpoint_b.clone());
let _router_b = iroh::protocol::Router::builder(endpoint_b.clone())
.accept(iroh_gossip::net::GOSSIP_ALPN, gossip_b.clone())
.spawn();
let room_b = IrohGossipState::new(endpoint_b.clone(), gossip_b.clone(), lookup_b.clone(), secret_b);
// 3. Create room on Node A
let topic_id = rand::random();
let ticket = peerspeak::network::PeerSpeakTicket {
host_addr: endpoint_a.addr(),
topic_id,
name: String::new(),
};
let ticket_str = ticket.to_string();
println!("Ticket generated: {}", ticket_str);
let state_a = PeerState {
name: "Alice".to_string(),
is_muted: false,
addr: endpoint_a.addr(),
sharing: None,
avatar: Default::default(),
game: None,
};
room_a.join(&ticket_str, state_a, vec![]).await?;
println!("Node A joined topic.");
// Subscribe to events on Node A
let mut rx_a = room_a.subscribe_events().await?;
tokio::spawn(async move {
while let Some(event) = rx_a.recv().await {
println!("Node A Event: {:?}", event);
}
});
// 4. Join room on Node B
let state_b = PeerState {
name: "Bob".to_string(),
is_muted: false,
addr: endpoint_b.addr(),
sharing: None,
avatar: Default::default(),
game: None,
};
room_b.join(&ticket_str, state_b, vec![]).await?;
println!("Node B joined topic.");
// Subscribe to events on Node B
let mut rx_b = room_b.subscribe_events().await?;
tokio::spawn(async move {
while let Some(event) = rx_b.recv().await {
println!("Node B Event: {:?}", event);
}
});
// Wait and check connection
println!("Waiting 10 seconds for Gossip sync...");
time::sleep(Duration::from_secs(10)).await;
println!("Alice's peers: {:?}", room_a.active_peers());
println!("Bob's peers: {:?}", room_b.active_peers());
println!("Test finished.");
Ok(())
}