code wiki / _hdl_build / nx_audio_serve_gate.nx
nx_audio_serve_gate.nx source
↩ module page · 96 lines · 7058 B
1// nx_audio_serve_gate.nx -- gate for the audiobook player BRAIN (SC34/35/37, server-side Nishi code). Sets up a
2// liberated M4B (fixture->m4b_book->render) then drives nx_audio_serve's handle-mode router with 4 real HTTP
3// requests and proves the daemon does the work the browser would otherwise need JS for:
4// 1. Range: bytes=4-11 -> 206 Partial Content + Content-Range + the exact bytes (so <audio> seeks/streams).
5// 2. ?sleep=1 -> the stream is CAPPED to 1*BYTES_PER_MIN (=100) -> SC34 sleep timer, server-side.
6// 3. POST /bookmark pos=137 -> 303 + the position stored in reader/<slug>/audio_pos.txt -> SC35.
7// 4. GET /resume -> returns {"pos":137} (cross-device resume) -> SC37.
8// GREEN = the player capabilities are NISHI-CODE APIs (no browser JS). expect_exit: 0 license_tier: ORIGINAL
9import "nx_syscalls.nx"
10
11func gp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
12func gnum(v0: i64) -> i64 { var v: i64=v0; if v<0 { sys_write(1,"-" as *u8,1); v=0-v } let b: *u8=sys_mmap(24); var k: i64=0; if v==0 {b[0]=48 as u8;k=1} while v>0 {b[k]=(48+(v%10)) as u8; v=v/10; k=k+1} let o: *u8=sys_mmap(24); var j: i64=0; while j<k {o[j]=b[k-1-j];j=j+1} sys_write(1,o,k); return 0 }
13func slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
14func ghas(hay: *u8, hl: i64, needle: *u8) -> i64 {
15 var nl: i64=0; while needle[nl]!=(0 as u8){nl=nl+1}
16 if nl == 0 { return 0 }
17 var i: i64 = 0
18 while i + nl <= hl { var k: i64=0; var hit: i64=1; while k<nl { if hay[i+k]!=needle[k]{hit=0;k=nl}else{k=k+1} } if hit==1 {return 1} i=i+1 }
19 return 0
20}
21func rd(path: *u8, lenp: *i64) -> *u8 { lenp[0]=0; return sys_read_file(path, lenp) }
22func wf(path: *u8, s: *u8) -> i64 { let fd: i64=sys_openat_wr(path, 0x1a4); if fd<0 {return 0-1} sys_write(fd, s, slen(s)); sys_close(fd); return 0 }
23func spawn(path: *u8, a0: *u8, a1: *u8, redir: *u8) -> i64 {
24 let pid: i64 = sys_fork()
25 if pid == 0 {
26 let fd: i64 = sys_openat_wr(redir, 420)
27 if fd >= 0 { sys_dup3(fd, 1, 0); sys_dup3(fd, 2, 0) }
28 let argv: *i64 = sys_mmap(64) as *i64
29 var n: i64 = 0
30 argv[0] = path as i64; n = 1
31 if (a0 as i64) != 0 { argv[n] = a0 as i64; n = n + 1 }
32 if (a1 as i64) != 0 { argv[n] = a1 as i64; n = n + 1 }
33 argv[n] = 0
34 let envp: *i64 = sys_mmap(16) as *i64
35 envp[0] = "PATH=/usr/bin:/bin\x00" as *u8 as i64; envp[1] = 0
36 sys_execve(path, argv, envp)
37 sys_exit(127)
38 }
39 let st: *i64 = sys_mmap(16) as *i64
40 sys_wait4(pid, st, 0)
41 return (st[0] >> 8) & 0xff
42}
43
44func main() -> i64 {
45 gp("=== nx_audio_serve_gate: audiobook player BRAIN (SC34/35/37) server-side via the Nishi daemon ===\n" as *u8)
46 let RUNNER: *u8 = "_offc/nx_sov_build_run.elf\x00" as *u8
47 let SC: *u8 = "knowledge/status/audio_serve_scratch.log\x00" as *u8
48 let SRV: *u8 = "_offc/nx_audio_serve.elf\x00" as *u8
49
50 // setup: liberate the M4B + render the player page
51 spawn(RUNNER, "nx_m4b_fixture\x00" as *u8, 0 as *u8, SC)
52 spawn(RUNNER, "nx_m4b_book\x00" as *u8, 0 as *u8, SC)
53 spawn(RUNNER, "nx_audio_render\x00" as *u8, 0 as *u8, SC)
54 spawn(RUNNER, "nx_audio_serve\x00" as *u8, 0 as *u8, SC) // refresh the twin
55
56 let RQ1: *u8 = "knowledge/status/asreq1.txt\x00" as *u8; let RP1: *u8 = "knowledge/status/asresp1.txt\x00" as *u8
57 let RQ2: *u8 = "knowledge/status/asreq2.txt\x00" as *u8; let RP2: *u8 = "knowledge/status/asresp2.txt\x00" as *u8
58 let RQ3: *u8 = "knowledge/status/asreq3.txt\x00" as *u8; let RP3: *u8 = "knowledge/status/asresp3.txt\x00" as *u8
59 let RQ4: *u8 = "knowledge/status/asreq4.txt\x00" as *u8; let RP4: *u8 = "knowledge/status/asresp4.txt\x00" as *u8
60
61 wf(RQ1, "GET /audio/nishi_m4b_fixture/track HTTP/1.1\r\nRange: bytes=4-11\r\nConnection: close\r\n\r\n" as *u8)
62 wf(RQ2, "GET /audio/nishi_m4b_fixture/track?sleep=1 HTTP/1.1\r\nConnection: close\r\n\r\n" as *u8)
63 wf(RQ3, "POST /audio/nishi_m4b_fixture/bookmark HTTP/1.1\r\nContent-Length: 7\r\nConnection: close\r\n\r\npos=137" as *u8)
64 wf(RQ4, "GET /audio/nishi_m4b_fixture/resume HTTP/1.1\r\nConnection: close\r\n\r\n" as *u8)
65
66 spawn(SRV, "handle\x00" as *u8, RQ1, RP1)
67 spawn(SRV, "handle\x00" as *u8, RQ2, RP2)
68 spawn(SRV, "handle\x00" as *u8, RQ3, RP3)
69 spawn(SRV, "handle\x00" as *u8, RQ4, RP4)
70
71 let l1: *i64 = sys_mmap(8) as *i64; let r1: *u8 = rd(RP1, l1); let n1: i64 = l1[0]
72 let l2: *i64 = sys_mmap(8) as *i64; let r2: *u8 = rd(RP2, l2); let n2: i64 = l2[0]
73 let l3: *i64 = sys_mmap(8) as *i64; let r3: *u8 = rd(RP3, l3); let n3: i64 = l3[0]
74 let l4: *i64 = sys_mmap(8) as *i64; let r4: *u8 = rd(RP4, l4); let n4: i64 = l4[0]
75 let lp: *i64 = sys_mmap(8) as *i64; let pf: *u8 = rd("knowledge/staging/media/reader/nishi_m4b_fixture/audio_pos.txt\x00" as *u8, lp); let pn: i64 = lp[0]
76
77 gp(" resp sizes: range=" as *u8); gnum(n1); gp(" sleep=" as *u8); gnum(n2); gp(" bookmark=" as *u8); gnum(n3); gp(" resume=" as *u8); gnum(n4); gp("\n" as *u8)
78
79 var pass: i64 = 0; var fail: i64 = 0
80 // 1. Range -> 206 + Content-Range + the exact track bytes (4..11 = "ftypM4B ")
81 if (r1 as i64)!=0 { if ghas(r1, n1, "206 Partial Content" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL range-not-206\n" as *u8) } } else { fail=fail+1; gp(" FAIL no-resp1\n" as *u8) }
82 if (r1 as i64)!=0 { if ghas(r1, n1, "Content-Range: bytes 4-11/224" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL bad-content-range\n" as *u8) } }
83 if (r1 as i64)!=0 { if ghas(r1, n1, "ftypM4B" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL range-bytes-wrong\n" as *u8) } }
84 // 2. sleep=1 -> Content-Length capped to 100
85 if (r2 as i64)!=0 { if ghas(r2, n2, "Content-Length: 100" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL sleep-not-capped\n" as *u8) } } else { fail=fail+1; gp(" FAIL no-resp2\n" as *u8) }
86 if (r2 as i64)!=0 { if ghas(r2, n2, "200 OK" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL sleep-not-200\n" as *u8) } }
87 // 3. bookmark -> 303 + stored position
88 if (r3 as i64)!=0 { if ghas(r3, n3, "303 See Other" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL bookmark-not-303\n" as *u8) } } else { fail=fail+1; gp(" FAIL no-resp3\n" as *u8) }
89 if (pf as i64)!=0 { if ghas(pf, pn, "137" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL pos-not-stored\n" as *u8) } } else { fail=fail+1; gp(" FAIL no-posfile\n" as *u8) }
90 // 4. resume -> {"pos":137}
91 if (r4 as i64)!=0 { if ghas(r4, n4, "\"pos\":137" as *u8)==1 { pass=pass+1 } else { fail=fail+1; gp(" FAIL resume-wrong\n" as *u8) } } else { fail=fail+1; gp(" FAIL no-resp4\n" as *u8) }
92
93 gp("AUDIO-SERVE-GATE pass=" as *u8); gnum(pass); gp(" fail=" as *u8); gnum(fail)
94 if fail == 0 { gp(" verdict=GREEN (player brain server-side: range-serve + sleep-cap + bookmark-store + resume; SC34/35/37 in Nishi code, 0 browser JS)\n" as *u8); sys_exit(0); return 0 }
95 gp(" verdict=RED\n" as *u8); sys_exit(1); return 1
96}