summaryrefslogtreecommitdiffstats
path: root/src/version.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
committersalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
commit058432130628d00764663ecf819a9adc6793768d (patch)
treed3f49f111fbfaa48647cca2600adcf9017af2d30 /src/version.rs
parentweb: serve latest.php through PHP and refuse stray PHP files (diff)
downloadzagan-bet-058432130628d00764663ecf819a9adc6793768d.tar.gz
zagan-bet-058432130628d00764663ecf819a9adc6793768d.zip
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.
Diffstat (limited to 'src/version.rs')
-rw-r--r--src/version.rs34
1 files changed, 30 insertions, 4 deletions
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 @@
5//! accepted until the server has answered it. 5//! accepted until the server has answered it.
6 6
7use std::env; 7use std::env;
8use std::time::{SystemTime, UNIX_EPOCH};
8 9
9/// Version this build reports. The client and the server share it. 10/// Version this build reports. The client and the server share it.
10pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 11pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -73,9 +74,9 @@ pub fn is_newer(latest: &str, current: &str) -> bool {
73 } 74 }
74} 75}
75 76
76/// The server line that accepts a handshake. 77/// The server line that accepts a handshake. `time` is Unix milliseconds.
77pub fn hello_line(version: &str, latest: &str, url: &str) -> String { 78pub fn hello_line(version: &str, latest: &str, url: &str, time: u64) -> String {
78 format!("HELLO {version} latest={latest} url={url}") 79 format!("HELLO {version} latest={latest} url={url} time={time}")
79} 80}
80 81
81#[derive(Debug, Clone, PartialEq, Eq)] 82#[derive(Debug, Clone, PartialEq, Eq)]
@@ -83,9 +84,11 @@ pub struct HelloReply {
83 pub version: String, 84 pub version: String,
84 pub latest: String, 85 pub latest: String,
85 pub url: String, 86 pub url: String,
87 /// Server clock at the moment the reply was built, Unix milliseconds.
88 pub time: u64,
86} 89}
87 90
88/// Parse `HELLO <version> latest=<version> url=<url>`. 91/// Parse `HELLO <version> latest=<version> url=<url> time=<unix_ms>`.
89pub fn parse_hello(line: &str) -> Option<HelloReply> { 92pub fn parse_hello(line: &str) -> Option<HelloReply> {
90 let rest = line.trim().strip_prefix("HELLO ")?; 93 let rest = line.trim().strip_prefix("HELLO ")?;
91 let mut tokens = rest.split_whitespace(); 94 let mut tokens = rest.split_whitespace();
@@ -93,11 +96,14 @@ pub fn parse_hello(line: &str) -> Option<HelloReply> {
93 96
94 let mut latest = None; 97 let mut latest = None;
95 let mut url = None; 98 let mut url = None;
99 let mut time = None;
96 for token in tokens { 100 for token in tokens {
97 if let Some(value) = token.strip_prefix("latest=") { 101 if let Some(value) = token.strip_prefix("latest=") {
98 latest = Some(value.to_string()); 102 latest = Some(value.to_string());
99 } else if let Some(value) = token.strip_prefix("url=") { 103 } else if let Some(value) = token.strip_prefix("url=") {
100 url = Some(value.to_string()); 104 url = Some(value.to_string());
105 } else if let Some(value) = token.strip_prefix("time=") {
106 time = value.parse().ok();
101 } 107 }
102 } 108 }
103 109
@@ -105,9 +111,29 @@ pub fn parse_hello(line: &str) -> Option<HelloReply> {
105 version, 111 version,
106 latest: latest?, 112 latest: latest?,
107 url: url?, 113 url: url?,
114 time: time?,
108 }) 115 })
109} 116}
110 117
118/// The reply to `PING`: `PONG <unix_ms>`.
119pub fn pong_line(time: u64) -> String {
120 format!("PONG {time}")
121}
122
123/// Parse a `PONG` reply's server time.
124pub fn parse_pong(line: &str) -> Option<u64> {
125 line.trim().strip_prefix("PONG ")?.trim().parse().ok()
126}
127
128/// Current Unix time in milliseconds. Used for handshake timestamps and crash
129/// anchors; a system clock before the epoch reads as zero.
130pub fn unix_time_ms() -> u64 {
131 SystemTime::now()
132 .duration_since(UNIX_EPOCH)
133 .map(|duration| duration.as_millis().min(u128::from(u64::MAX)) as u64)
134 .unwrap_or(0)
135}
136
111/// Download page advertised by the server. `DOWNLOAD_URL` wins when set. 137/// Download page advertised by the server. `DOWNLOAD_URL` wins when set.
112pub fn download_url() -> String { 138pub fn download_url() -> String {
113 env::var("DOWNLOAD_URL") 139 env::var("DOWNLOAD_URL")