code wiki / (root) / nx_torrent_daemon_gate.nx

nx_torrent_daemon_gate.nx source

↩ module page · 154 lines · 10488 B

1// nx_torrent_daemon_gate.nx -- SOVEREIGN verification of the MULTI-TORRENT /torrent daemon 2// (X-TORRENT-UI-003). Drives the LIVE daemon via nx_http_client; asserts the surface, the login 3// hide-fix, multi-torrent isolation (two DISTINCT magnets -> two independent rows, per-id 4// pause/remove with no clobber), AND the browser urlencoded-form add path (bug#1 regression guard: 5// the daemon's d_form_magnet decode, which the old JSON-only gate never exercised). NO bash/curl. 6// Run: ./_offc/nx_sov_build_run.elf nx_torrent_daemon_gate (daemon must be UP via nx_torrent_up) 7// license_tier: ORIGINAL layer: gate over L6 daemon 8// module: nishi-core.torrent.daemon_gate 9// depends: nishi-core.http_client, nishi-core.syscalls 10import "nx_http_client.nx" 11 12import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 13import "nx_gate_verdict.nx" 14func g_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 15func g_putn(v: i64) -> i64 { 16 let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } 17 let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 18 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 19 var i: i64 = 0; while i < k { bb[i] = t[k-1-i]; i = i + 1 } 20 sys_write(1, bb, k); return 0 21} 22func g_strlen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 23func g_contains(buf: *u8, n: i64, s: *u8) -> i64 { 24 let sl: i64 = g_strlen(s); if sl == 0 { return 1 } 25 var i: i64 = 0 26 while i + sl <= n { 27 var j: i64 = 0; var ok: i64 = 1 28 while j < sl { if buf[i+j] != s[j] { ok = 0; j = sl } else { j = j + 1 } } 29 if ok == 1 { return 1 } 30 i = i + 1 31 } 32 return 0 33} 34func g_report(label: *u8, ok: i64) -> i64 { 35 if ok == 1 { g_puts(" [PASS] " as *u8) } else { g_puts(" [FAIL] " as *u8) } 36 g_puts(label); g_puts("\n" as *u8); return 0 37} 38func g_get(path: *u8, buf: *u8, cap: i64) -> i64 { 39 let addr: *u8 = sys_mmap(16); nx_http_client_sockaddr_ipv4(addr, 127, 0, 0, 1, 8097) 40 let v: *i64 = sys_mmap(16) as *i64 41 return nx_http_client_get(addr, path, g_strlen(path), "127.0.0.1" as *u8, 9, buf, cap, v) 42} 43func g_post(path: *u8, body: *u8, blen: i64, buf: *u8, cap: i64) -> i64 { 44 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); if fd < 0 { return 0 - 1 } 45 let addr: *u8 = sys_mmap(16); nx_http_client_sockaddr_ipv4(addr, 127, 0, 0, 1, 8097) 46 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 47 sys_set_socket_timeout(fd, 8) // no-hang law: bound the read so a server-side fd leak can't wedge the gate 48 let req: *u8 = sys_mmap(8192) 49 let rl: i64 = nx_http_client_build_request_post(path, g_strlen(path), "127.0.0.1" as *u8, 9, 50 "application/json" as *u8, 16, body, blen, req) 51 sys_write(fd, req, rl) 52 let got: i64 = _drain(fd, buf, cap) 53 sys_close(fd) 54 return got 55} 56// Same as g_post but Content-Type: application/x-www-form-urlencoded -- the EXACT request the browser 57// UI sends for an add (body = magnet=<encodeURIComponent(...)>&area=...). This drives the daemon's 58// d_form_magnet decode path (bug#1), which g_post's JSON path never exercised. 59func g_post_form(path: *u8, body: *u8, blen: i64, buf: *u8, cap: i64) -> i64 { 60 let fd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0); if fd < 0 { return 0 - 1 } 61 let addr: *u8 = sys_mmap(16); nx_http_client_sockaddr_ipv4(addr, 127, 0, 0, 1, 8097) 62 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 63 sys_set_socket_timeout(fd, 8) // no-hang law: bound the read so a server-side fd leak can't wedge the gate 64 let req: *u8 = sys_mmap(8192) 65 let rl: i64 = nx_http_client_build_request_post(path, g_strlen(path), "127.0.0.1" as *u8, 9, 66 "application/x-www-form-urlencoded" as *u8, 33, body, blen, req) 67 sys_write(fd, req, rl) 68 let got: i64 = _drain(fd, buf, cap) 69 sys_close(fd) 70 return got 71} 72 73func main() -> i64 { 74 let buf: *u8 = sys_mmap(262144) 75 var pass: i64 = 0; var tot: i64 = 0 76 77 // --- surface: the SESSION-2 redesign is one all-inline sovereign page (no external app.js, no 78 // #login element). The old gate asserted /torrent/app.js + #login[hidden]; the redesign removed both, 79 // so those assertions were stale. Assert the redesigned surface AND its sovereignty instead. --- 80 var n: i64 = g_get("/torrent" as *u8, buf, 262144) 81 var ok1: i64 = 0 82 if g_contains(buf, n, "200 OK" as *u8) == 1 { if g_contains(buf, n, "Nishi Media" as *u8) == 1 { if g_contains(buf, n, "id=chips" as *u8) == 1 { ok1 = 1 } } } 83 tot = tot + 1; if ok1 == 1 { pass = pass + 1 } g_report("GET /torrent = 200 + redesigned UI (Nishi Media + batch-add chips)" as *u8, ok1) 84 85 n = g_get("/torrent" as *u8, buf, 262144) 86 var okjs: i64 = 0 87 if g_contains(buf, n, "<script>" as *u8) == 1 { if g_contains(buf, n, "classify(" as *u8) == 1 { if g_contains(buf, n, "app.js" as *u8) == 0 { okjs = 1 } } } 88 tot = tot + 1; if okjs == 1 { pass = pass + 1 } g_report("GET /torrent = sovereign inline client (bare <script> + classify(), zero external app.js)" as *u8, okjs) 89 90 n = g_get("/torrent/api/me" as *u8, buf, 262144) 91 let okme: i64 = g_contains(buf, n, "local" as *u8) 92 tot = tot + 1; if okme == 1 { pass = pass + 1 } g_report("GET /api/me = auth-open" as *u8, okme) 93 94 // --- MULTI-TORRENT: two DISTINCT magnets --- 95 let m1: *u8 = "{\"magnet\":\"magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny&tr=udp://open.stealth.si:80/announce\"}" as *u8 96 let m2: *u8 = "{\"magnet\":\"magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp://open.stealth.si:80/announce\"}" as *u8 97 n = g_post("/torrent/api/add" as *u8, m1, g_strlen(m1), buf, 262144) 98 let oka1: i64 = g_contains(buf, n, "Big_Buck_Bunny" as *u8) 99 tot = tot + 1; if oka1 == 1 { pass = pass + 1 } g_report("POST /add #1 (BBB) -> 200 + dn-derived name id Big_Buck_Bunny" as *u8, oka1) 100 n = g_post("/torrent/api/add" as *u8, m2, g_strlen(m2), buf, 262144) 101 let oka2: i64 = g_contains(buf, n, "Sintel" as *u8) 102 tot = tot + 1; if oka2 == 1 { pass = pass + 1 } g_report("POST /add #2 (Sintel) -> 200 + name id Sintel" as *u8, oka2) 103 104 sys_sleep_ms(2000) 105 n = g_get("/torrent/api/status" as *u8, buf, 262144) 106 var okboth: i64 = 0; if g_contains(buf, n, "Big_Buck_Bunny" as *u8) == 1 { if g_contains(buf, n, "Sintel" as *u8) == 1 { okboth = 1 } } 107 tot = tot + 1; if okboth == 1 { pass = pass + 1 } g_report("GET /status = BOTH torrents at once (no clobber)" as *u8, okboth) 108 109 g_post("/torrent/api/Big_Buck_Bunny/pause" as *u8, "" as *u8, 0, buf, 262144) 110 n = g_get("/torrent/api/status" as *u8, buf, 262144) 111 var okp: i64 = 0; if g_contains(buf, n, "paused" as *u8) == 1 { if g_contains(buf, n, "Sintel" as *u8) == 1 { okp = 1 } } 112 tot = tot + 1; if okp == 1 { pass = pass + 1 } g_report("pause #1 -> Big_Buck_Bunny paused while Sintel keeps running" as *u8, okp) 113 114 g_post("/torrent/api/Big_Buck_Bunny/remove" as *u8, "" as *u8, 0, buf, 262144) 115 n = g_get("/torrent/api/status" as *u8, buf, 262144) 116 var okr1: i64 = 0; if g_contains(buf, n, "Sintel" as *u8) == 1 { if g_contains(buf, n, "Big_Buck_Bunny" as *u8) == 0 { okr1 = 1 } } 117 tot = tot + 1; if okr1 == 1 { pass = pass + 1 } g_report("remove #1 -> only Sintel remains (per-id isolation)" as *u8, okr1) 118 119 g_post("/torrent/api/Sintel/remove" as *u8, "" as *u8, 0, buf, 262144) 120 n = g_get("/torrent/api/status" as *u8, buf, 262144) 121 var okr2: i64 = 0; if g_contains(buf, n, "Sintel" as *u8) == 0 { if g_contains(buf, n, "Big_Buck_Bunny" as *u8) == 0 { okr2 = 1 } } 122 tot = tot + 1; if okr2 == 1 { pass = pass + 1 } g_report("remove #2 -> both test torrents gone (robust to other torrents)" as *u8, okr2) 123 124 // --- BUG#1 REGRESSION GUARD: the BROWSER add path = urlencoded FORM POST. The UI sends 125 // Content-Type: application/x-www-form-urlencoded, body magnet=<encodeURIComponent(magnet)>&area=... 126 // The daemon decodes it in d_form_magnet (keeps the magnet's internal %26, stops at the real & before 127 // area=). bug#1 was that the daemon only scanned for a literal "magnet:" and never url-decoded, so the 128 // browser add 400'd while THIS gate (JSON-only) stayed GREEN -- the classic gate-vs-browser blind spot. 129 // The encoded magnet carries an internal %26 (before dn=) and a real & (before area=) -> exercises both 130 // halves of the d_form_magnet fix. Success = a real id was ASSIGNED (NOT the "no magnet" error bug#1 131 // produced); the id is the dn-name (fresh) or a dedup'd name -- either way the urlencoded decode worked. 132 let fbody: *u8 = "magnet=magnet%3A%3Fxt%3Durn%3Abtih%3Add8255ecdc7ca55fb0bbf81323d87062db1f6d1c%26dn%3DBig_Buck_Bunny&area=media" as *u8 133 n = g_post_form("/torrent/api/add" as *u8, fbody, g_strlen(fbody), buf, 262144) 134 var okform: i64 = 0 135 if g_contains(buf, n, "\"id\":\"" as *u8) == 1 { if g_contains(buf, n, "no magnet" as *u8) == 0 { if g_contains(buf, n, "error" as *u8) == 0 { okform = 1 } } } 136 tot = tot + 1; if okform == 1 { pass = pass + 1 } g_report("POST /add urlencoded FORM (browser path / d_form_magnet) ACCEPTED -- bug#1 guard" as *u8, okform) 137 138 // cleanup: dn=Big_Buck_Bunny -> deterministic id, so remove by name; assert it's gone -- leave no download. 139 g_post("/torrent/api/Big_Buck_Bunny/remove" as *u8, "" as *u8, 0, buf, 262144) 140 n = g_get("/torrent/api/status" as *u8, buf, 262144) 141 var okfc: i64 = 0; if g_contains(buf, n, "Big_Buck_Bunny" as *u8) == 0 { okfc = 1 } 142 tot = tot + 1; if okfc == 1 { pass = pass + 1 } g_report("form-add cleaned up (removed by id, gone from status)" as *u8, okfc) 143 144 g_puts("TORRENT-MULTI-GATE authored=organ pass=" as *u8); g_putn(pass); g_puts("/" as *u8); g_putn(tot) 145 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check 146 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled 147 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify. 148 let ctr__dry: *i64 = gv_ctr() 149 ctr__dry[0] = pass 150 ctr__dry[1] = tot 151 let rc__dry: i64 = gv_verdict("TORRENT-DAEMON-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8) 152 sys_exit(rc__dry) 153 return rc__dry 154}