code wiki / _hdl_build / nx_mp_daemon.nx
nx_mp_daemon.nx source
↩ module page · 119 lines · 5422 B
1// nx_mp_daemon.nx -- sovereign request-handler for the multiplayer player-state relay (the /pstate POST +
2// /roster GET routes). A LIBRARY (no main) so it is gate-testable without sockets and then dropped into the
3// socket accept/fork loop (mirrors nx_arcade_serve). The relay lives in SHARED mmap so every forked handler
4// sees it. Composes nx_room_relay (rr_post) + nx_mp_relay (mp_roster_body). Pure parsing + buffer ops.
5// /pstate?r=<room>&p=<peer> (POST, body = the wasm's packed snapshot bytes) -> rr_post into the room
6// /roster?r=<room>&p=<self> (GET) -> mp_roster_body of the OTHER live peers, as an octet-stream body
7// The browser host does ONLY: snapshot->POST, GET->memcpy->gi_net_ingest_roster. Zero net logic in JS.
8// license_tier: ORIGINAL
9import "nx_syscalls.nx"
10import "nx_mp_relay.nx" // brings nx_room_relay (rr_*) transitively
11const K_MAGIC_4096: i64 = 4096
12
13// extract the request-target (the path) from "METHOD <target> HTTP/1.1" into out; returns its length.
14func mpd_path(req: *u8, n: i64, out: *u8) -> i64 {
15 var i: i64 = 0
16 var sp: i64 = 0 - 1
17 while i < n { if req[i] == (32 as u8) { sp = i; i = n } else { i = i + 1 } } // first space = end of METHOD
18 if sp < 0 { out[0] = 0 as u8; return 0 }
19 var p: i64 = sp + 1
20 var w: i64 = 0
21 var go: i64 = 1
22 while go == 1 {
23 if p >= n { go = 0 } else {
24 if req[p] == (32 as u8) { go = 0 } else { out[w] = req[p]; w = w + 1; p = p + 1 }
25 }
26 }
27 out[w] = 0 as u8
28 return w
29}
30func mpd_starts(s: *u8, pre: *u8) -> i64 {
31 var i: i64 = 0
32 while pre[i] != (0 as u8) { if s[i] != pre[i] { return 0 } i = i + 1 }
33 return 1
34}
35// route a path: 1 = /pstate, 2 = /roster, 0 = neither (caller serves files / 404)
36func mpd_route(path: *u8) -> i64 {
37 if mpd_starts(path, "/pstate" as *u8) == 1 { return 1 }
38 if mpd_starts(path, "/roster" as *u8) == 1 { return 2 }
39 return 0
40}
41func mpd_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
42// parse the unsigned integer following `key` in `path` (e.g. "r="); returns the value, or -1 if absent.
43func mpd_qint(path: *u8, key: *u8) -> i64 {
44 let n: i64 = mpd_slen(path)
45 let kl: i64 = mpd_slen(key)
46 var st: i64 = 0 - 1
47 var i: i64 = 0
48 while i + kl <= n {
49 var j: i64 = 0
50 var m: i64 = 1
51 while j < kl { if path[i + j] != key[j] { m = 0; j = kl } else { j = j + 1 } }
52 if m == 1 { st = i + kl; i = n + kl } else { i = i + 1 }
53 }
54 if st < 0 { return 0 - 1 }
55 var v: i64 = 0
56 var p: i64 = st
57 var any: i64 = 0
58 var go: i64 = 1
59 while go == 1 {
60 if p >= n { go = 0 } else {
61 let c: i64 = path[p] as i64
62 if c >= 48 { if c <= 57 { v = v * 10 + (c - 48); any = 1; p = p + 1 } else { go = 0 } } else { go = 0 }
63 }
64 }
65 if any == 0 { return 0 - 1 }
66 return v
67}
68// byte offset of the POST body (just past the "\r\n\r\n" header terminator), or -1 if none.
69func mpd_body_off(req: *u8, n: i64) -> i64 {
70 var i: i64 = 0
71 while i + 3 < n {
72 if req[i] == (13 as u8) { if req[i+1] == (10 as u8) { if req[i+2] == (13 as u8) { if req[i+3] == (10 as u8) { return i + 4 } } } }
73 i = i + 1
74 }
75 return 0 - 1
76}
77func mpd_wstr(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { buf[off + i] = s[i]; i = i + 1 } return off + i }
78func mpd_wn(buf: *u8, off: i64, v: i64) -> i64 {
79 var m: i64 = v
80 let tmp: *u8 = sys_mmap(32)
81 var k: i64 = 0
82 if m == 0 { tmp[0] = 48 as u8; k = 1 }
83 while m > 0 { tmp[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
84 var i: i64 = 0
85 while i < k { buf[off + i] = tmp[k - 1 - i]; i = i + 1 }
86 return off + k
87}
88
89// HANDLE /pstate: parse room+peer, take the POST body as the snapshot, rr_post it. Writes a tiny 200 ack to
90// `out`; returns response length. (st/arena = the SHARED relay; slotb = its slot bytes; now = wall ms.)
91func mpd_pstate(req: *u8, n: i64, path: *u8, st: *i64, arena: *u8, slotb: i64, now: i64, out: *u8) -> i64 {
92 let rm: i64 = mpd_qint(path, "r=" as *u8)
93 let pr: i64 = mpd_qint(path, "p=" as *u8)
94 let bo: i64 = mpd_body_off(req, n)
95 if rm >= 0 { if pr >= 0 { if bo >= 0 {
96 var bl: i64 = n - bo
97 if bl > slotb { bl = slotb }
98 rr_post(st, arena, rm, pr, now, now, (req + bo) as *u8, bl)
99 } } }
100 var o: i64 = 0
101 o = mpd_wstr(out, o, "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\n\r\nok" as *u8)
102 return o
103}
104// HANDLE /roster: serialize OTHER live peers (mp_roster_body) as an octet-stream body. Writes the full HTTP
105// response (header + binary body) to `out`; returns total length.
106func mpd_roster(path: *u8, st: *i64, arena: *u8, slotb: i64, now: i64, ttl: i64, out: *u8) -> i64 {
107 let rm: i64 = mpd_qint(path, "r=" as *u8)
108 let self_pr: i64 = mpd_qint(path, "p=" as *u8)
109 let body: *u8 = sys_mmap(K_MAGIC_4096)
110 var bl: i64 = 0
111 if rm >= 0 { bl = mp_roster_body(st, arena, slotb, rm, self_pr, now, ttl, body) }
112 var o: i64 = 0
113 o = mpd_wstr(out, o, "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: " as *u8)
114 o = mpd_wn(out, o, bl)
115 o = mpd_wstr(out, o, "\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\n\r\n" as *u8)
116 var k: i64 = 0
117 while k < bl { out[o + k] = body[k]; k = k + 1 }
118 return o + bl
119}