From 058432130628d00764663ecf819a9adc6793768d Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 8 Nov 2026 03:29:35 +0100 Subject: v5.0.0: protocol v5 and the browser frontend Protocol v5 replaces newline lines with bounded [u32 len][text] frames and REQ/RES/EVT envelopes. Requests carry correlation ids and the server replays the last 256 replies per connection, so a gateway retry cannot double-debit and a late reply cannot clobber a newer request. HELLO/PONG carry server time; crash rounds announce started_at and push coalesced 10 Hz ticks. Crash now commits sha256(seed) before a round and reveals the seed when it ends, so clients recompute the crash point. Adds the webd/ gateway: one blocking thread, one ClientCore and one pinned upstream TLS connection per session, an HttpOnly session cookie, takeover, and a 60-second grace window. The browser sends Command JSON and renders View JSON; it never speaks the game protocol or stores credentials. Adds the vanilla TypeScript browser app at /play/ (login, lobby, all four games, history, notices, crash verification), the nginx /play and /ws routes, and the headless DOM smoke test. webd and the app stay out of the TUI/GUI/server build graph. The client/server version moves to 5.0.0 in lockstep; v4 peers are refused at HELLO. --- tests/version.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 9 deletions(-) (limited to 'tests/version.rs') diff --git a/tests/version.rs b/tests/version.rs index af244db..31bc2b6 100644 --- a/tests/version.rs +++ b/tests/version.rs @@ -1,5 +1,6 @@ use zagan_bet::version::{ - compatible, hello_line, is_newer, parse_hello, Version, VERSION, + VERSION, Version, compatible, hello_line, is_newer, parse_hello, parse_pong, pong_line, + unix_time_ms, }; #[test] @@ -24,16 +25,16 @@ fn versions_parse_and_reject_nonsense() { Version::parse("4.0.0-rc1").map(|v| (v.major, v.minor, v.patch)), Some((4, 0, 0)) ); - assert_eq!( - Version::parse("4.0.0+build7").map(|v| v.patch), - Some(0) - ); + assert_eq!(Version::parse("4.0.0+build7").map(|v| v.patch), Some(0)); for bad in ["", "4", "4.0", "4.0.0.1", "x.0.0", "4.x.0", "4.0.0-"] { assert!(Version::parse(bad).is_none(), "{bad:?} should not parse"); } - assert!(Version::parse(VERSION).is_some(), "the build version must parse"); + assert!( + Version::parse(VERSION).is_some(), + "the build version must parse" + ); } #[test] @@ -57,19 +58,45 @@ fn newer_versions_are_ordered_by_major_minor_patch() { #[test] fn the_hello_reply_round_trips() { - let line = hello_line("4.0.0", "4.1.2", "https://zagan.example/"); + let line = hello_line( + "4.0.0", + "4.1.2", + "https://zagan.example/", + 1_700_000_000_123, + ); let reply = parse_hello(&line).expect("parse a generated reply"); assert_eq!(reply.version, "4.0.0"); assert_eq!(reply.latest, "4.1.2"); assert_eq!(reply.url, "https://zagan.example/"); + assert_eq!(reply.time, 1_700_000_000_123); - assert!(parse_hello("HELLO 4.0.0 url=https://x/").is_none(), "latest is required"); - assert!(parse_hello("HELLO 4.0.0 latest=4.0.0").is_none(), "url is required"); + assert!( + parse_hello("HELLO 4.0.0 url=https://x/ time=1").is_none(), + "latest is required" + ); + assert!( + parse_hello("HELLO 4.0.0 latest=4.0.0 time=1").is_none(), + "url is required" + ); + assert!( + parse_hello("HELLO 4.0.0 latest=4.0.0 url=https://x/").is_none(), + "time is required" + ); assert!(parse_hello("PONG").is_none()); assert!(parse_hello("HELLO").is_none()); } +#[test] +fn pong_carries_the_server_time() { + let line = pong_line(1_700_000_000_456); + assert_eq!(line, "PONG 1700000000456"); + assert_eq!(parse_pong(&line), Some(1_700_000_000_456)); + assert!(parse_pong("PONG").is_none()); + assert!(parse_pong("RES 1 PONG 1700000000456").is_none()); + assert!(unix_time_ms() > 1_600_000_000_000); +} + /// The GUI is a client, so it must ship in lockstep with the TUI and server. /// Keep `gui/Cargo.toml` at the same version as the root package. #[test] @@ -83,6 +110,18 @@ fn the_gui_member_uses_the_same_version() { assert_eq!(gui_version, VERSION); } +/// The gateway is a frontend too; keep it in lockstep with the core. +#[test] +fn the_webd_member_uses_the_same_version() { + let manifest = include_str!("../webd/Cargo.toml"); + let version = manifest + .lines() + .find_map(|line| line.trim().strip_prefix("version = ")) + .expect("webd/Cargo.toml declares a version") + .trim_matches('"'); + assert_eq!(version, VERSION); +} + /// egui's dependency tree must never enter the TUI or server build graph. /// The GUI is a workspace member that depends on this package, never the /// other way round. -- cgit v1.2.3