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. --- src/version.rs | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) (limited to 'src/version.rs') diff --git a/src/version.rs b/src/version.rs index c1dcee5..adc2a8d 100644 --- a/src/version.rs +++ b/src/version.rs @@ -5,6 +5,7 @@ //! accepted until the server has answered it. use std::env; +use std::time::{SystemTime, UNIX_EPOCH}; /// Version this build reports. The client and the server share it. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -73,9 +74,9 @@ pub fn is_newer(latest: &str, current: &str) -> bool { } } -/// The server line that accepts a handshake. -pub fn hello_line(version: &str, latest: &str, url: &str) -> String { - format!("HELLO {version} latest={latest} url={url}") +/// The server line that accepts a handshake. `time` is Unix milliseconds. +pub fn hello_line(version: &str, latest: &str, url: &str, time: u64) -> String { + format!("HELLO {version} latest={latest} url={url} time={time}") } #[derive(Debug, Clone, PartialEq, Eq)] @@ -83,9 +84,11 @@ pub struct HelloReply { pub version: String, pub latest: String, pub url: String, + /// Server clock at the moment the reply was built, Unix milliseconds. + pub time: u64, } -/// Parse `HELLO latest= url=`. +/// Parse `HELLO latest= url= time=`. pub fn parse_hello(line: &str) -> Option { let rest = line.trim().strip_prefix("HELLO ")?; let mut tokens = rest.split_whitespace(); @@ -93,11 +96,14 @@ pub fn parse_hello(line: &str) -> Option { let mut latest = None; let mut url = None; + let mut time = None; for token in tokens { if let Some(value) = token.strip_prefix("latest=") { latest = Some(value.to_string()); } else if let Some(value) = token.strip_prefix("url=") { url = Some(value.to_string()); + } else if let Some(value) = token.strip_prefix("time=") { + time = value.parse().ok(); } } @@ -105,9 +111,29 @@ pub fn parse_hello(line: &str) -> Option { version, latest: latest?, url: url?, + time: time?, }) } +/// The reply to `PING`: `PONG `. +pub fn pong_line(time: u64) -> String { + format!("PONG {time}") +} + +/// Parse a `PONG` reply's server time. +pub fn parse_pong(line: &str) -> Option { + line.trim().strip_prefix("PONG ")?.trim().parse().ok() +} + +/// Current Unix time in milliseconds. Used for handshake timestamps and crash +/// anchors; a system clock before the epoch reads as zero. +pub fn unix_time_ms() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64) + .unwrap_or(0) +} + /// Download page advertised by the server. `DOWNLOAD_URL` wins when set. pub fn download_url() -> String { env::var("DOWNLOAD_URL") -- cgit v1.2.3