nx_hls_parse.nx source
↩ module page · 184 lines · 9271 B
1// nx_hls_parse.nx -- X-DLP rung 0: the sovereign HLS (.m3u8) manifest PARSER.
2// The FLOOR of yt-dlp-class media download: an HLS stream is described by an
3// M3U8 playlist (RFC 8216). A MASTER playlist lists variant streams
4// (#EXT-X-STREAM-INF:BANDWIDTH=... then a variant URI); a MEDIA playlist lists
5// segments (#EXTINF:<dur>, then a segment URI). This parser walks the text
6// (no JS, no curl) into a flat entry table the downloader then fetches via the
7// already-sovereign h2/https client. Non-JS path = independent of the JS engine.
8//
9// API: nx_hls_parse(buf, len, out, max_entries, meta) -> entry_count (>=0), or -1
10// if the text is not a valid playlist (missing #EXTM3U header -- tamper-honest).
11// out[i*3+0] = URI start offset in buf
12// out[i*3+1] = URI byte length
13// out[i*3+2] = value: segment DURATION in millis (media) OR BANDWIDTH bps (master)
14// meta[0] = is_master (0|1) meta[1] = target_duration sec meta[2] = count
15// meta[3] = version
16//
17// GATE (main): KATs parse a real MEDIA playlist (3 segments, durations) + a MASTER
18// playlist (2 variants, bandwidths) + a TAMPER case (non-m3u8 -> -1, never faked).
19// license_tier: ORIGINAL (sovereign text STATE_MACHINE; team re-authors from the spec).
20import "nx_syscalls.nx"
21import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
22
23func hls_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
24func hls_line_end(buf: *u8, len: i64, p: i64) -> i64 { var e: i64 = p; while e < len { if buf[e] == (10 as u8) { return e } e = e + 1 } return e }
25func hls_starts(buf: *u8, len: i64, p: i64, lit: *u8) -> i64 {
26 var i: i64 = 0
27 while lit[i] != (0 as u8) {
28 if p + i >= len { return 0 }
29 if (buf[p + i] & 0xff) != (lit[i] & 0xff) { return 0 }
30 i = i + 1
31 }
32 return 1
33}
34func hls_parse_uint(buf: *u8, len: i64, p: i64) -> i64 {
35 var n: i64 = 0
36 var i: i64 = p
37 var go: i64 = 1
38 while go == 1 {
39 if i >= len { go = 0 }
40 else { let c: i64 = buf[i] & 0xff; if c >= 48 { if c <= 57 { n = n * 10 + (c - 48); i = i + 1 } else { go = 0 } } else { go = 0 } }
41 }
42 return n
43}
44// parse "9.009" -> 9009 millis (integer secs * 1000 + up to 3 fractional digits).
45func hls_parse_dur_millis(buf: *u8, len: i64, p: i64) -> i64 {
46 var whole: i64 = 0
47 var i: i64 = p
48 var go: i64 = 1
49 while go == 1 {
50 if i >= len { go = 0 }
51 else { let c: i64 = buf[i] & 0xff; if c >= 48 { if c <= 57 { whole = whole * 10 + (c - 48); i = i + 1 } else { go = 0 } } else { go = 0 } }
52 }
53 var millis: i64 = whole * 1000
54 if i < len { if (buf[i] & 0xff) == 46 { // '.'
55 i = i + 1
56 var scale: i64 = 100
57 var g2: i64 = 1
58 while g2 == 1 {
59 if i >= len { g2 = 0 }
60 else { let c: i64 = buf[i] & 0xff
61 if c >= 48 { if c <= 57 { if scale >= 1 { millis = millis + (c - 48) * scale } scale = scale / 10; i = i + 1 } else { g2 = 0 } } else { g2 = 0 } }
62 }
63 } }
64 return millis
65}
66// find NUL-term lit within buf[from:to); return index or -1.
67func hls_find(buf: *u8, from: i64, to: i64, lit: *u8) -> i64 {
68 let ll: i64 = hls_slen(lit)
69 if ll == 0 { return 0 - 1 }
70 var i: i64 = from
71 while i + ll <= to {
72 var j: i64 = 0
73 var hit: i64 = 1
74 while j < ll { if (buf[i + j] & 0xff) != (lit[j] & 0xff) { hit = 0; j = ll } else { j = j + 1 } }
75 if hit == 1 { return i }
76 i = i + 1
77 }
78 return 0 - 1
79}
80
81func nx_hls_parse(buf: *u8, len: i64, out: *i64, max_entries: i64, meta: *i64) -> i64 {
82 meta[0] = 0; meta[1] = 0; meta[2] = 0; meta[3] = 0
83 if hls_starts(buf, len, 0, "#EXTM3U\x00" as *u8) == 0 { return 0 - 1 } // not an M3U8
84 var p: i64 = hls_line_end(buf, len, 0) + 1
85 var count: i64 = 0
86 var pending: i64 = 0 // 0=none, 1=media(dur), 2=master(bw)
87 var pval: i64 = 0
88 while p < len {
89 let e: i64 = hls_line_end(buf, len, p)
90 var lend: i64 = e
91 if lend > p { if buf[lend - 1] == (13 as u8) { lend = lend - 1 } } // trim \r
92 let llen: i64 = lend - p
93 if llen > 0 {
94 if (buf[p] & 0xff) == 35 { // '#' -> tag line
95 if hls_starts(buf, len, p, "#EXTINF:\x00" as *u8) == 1 { pending = 1; pval = hls_parse_dur_millis(buf, len, p + 8) }
96 else { if hls_starts(buf, len, p, "#EXT-X-STREAM-INF:\x00" as *u8) == 1 {
97 meta[0] = 1; pending = 2
98 let bw: i64 = hls_find(buf, p, lend, "BANDWIDTH=\x00" as *u8)
99 if bw >= 0 { pval = hls_parse_uint(buf, len, bw + 10) } else { pval = 0 }
100 } else { if hls_starts(buf, len, p, "#EXT-X-TARGETDURATION:\x00" as *u8) == 1 { meta[1] = hls_parse_uint(buf, len, p + 22) }
101 else { if hls_starts(buf, len, p, "#EXT-X-VERSION:\x00" as *u8) == 1 { meta[3] = hls_parse_uint(buf, len, p + 15) } } } }
102 } else { // URI line
103 if pending != 0 {
104 if count < max_entries {
105 out[count * 3 + 0] = p
106 out[count * 3 + 1] = llen
107 out[count * 3 + 2] = pval
108 count = count + 1
109 }
110 pending = 0; pval = 0
111 }
112 }
113 }
114 p = e + 1
115 }
116 meta[2] = count
117 return count
118}
119
120// ===================== GATE =====================
121func hp_puts(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
122// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
123// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
124// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
125// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
126func hp_putn(v: i64) -> i64 { nxi_out(v); return 0 }
127func hp_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
128// does out-entry k's URI byte-match expected NUL-term string?
129func hp_uri_eq(buf: *u8, out: *i64, k: i64, want: *u8) -> i64 {
130 let st: i64 = out[k*3+0]; let ln: i64 = out[k*3+1]
131 if ln != hp_slen(want) { return 0 }
132 var i: i64 = 0
133 while i < ln { if (buf[st+i] & 0xff) != (want[i] & 0xff) { return 0 } i = i + 1 }
134 return 1
135}
136
137func main() -> i64 {
138 var pass: i64 = 0
139 var tot: i64 = 0
140 hp_puts("nx_hls_parse gate (X-DLP rung 0, RFC 8216 M3U8)\n" as *u8)
141 let out: *i64 = sys_mmap(8 * 3 * 256) as *i64
142 let meta: *i64 = sys_mmap(8 * 8) as *i64
143
144 // KAT1: MEDIA playlist -> 3 segments, durations, target=10, not master
145 let m1: *u8 = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:10\n#EXT-X-MEDIA-SEQUENCE:0\n#EXTINF:9.009,\nseg0.ts\n#EXTINF:9.009,\nseg1.ts\n#EXTINF:3.003,\nseg2.ts\n#EXT-X-ENDLIST\n\x00" as *u8
146 let c1: i64 = nx_hls_parse(m1, hp_slen(m1), out, 256, meta)
147 var r1: i64 = 1
148 if c1 != 3 { r1 = 0 }
149 if meta[0] != 0 { r1 = 0 } // not master
150 if meta[1] != 10 { r1 = 0 } // target duration
151 if meta[3] != 3 { r1 = 0 } // version
152 if r1 == 1 { if hp_uri_eq(m1, out, 0, "seg0.ts\x00" as *u8) == 0 { r1 = 0 } }
153 if r1 == 1 { if out[0*3+2] != 9009 { r1 = 0 } } // 9.009s -> 9009 ms
154 if r1 == 1 { if hp_uri_eq(m1, out, 2, "seg2.ts\x00" as *u8) == 0 { r1 = 0 } }
155 if r1 == 1 { if out[2*3+2] != 3003 { r1 = 0 } }
156 if r1 == 1 { hp_puts(" PASS KAT1 media playlist: 3 segs + durations + target\n" as *u8); pass=pass+1 } else { hp_puts(" FAIL KAT1 media (count=" as *u8); hp_putn(c1); hp_puts(")\n" as *u8) }
157 tot = tot + 1
158
159 // KAT2: MASTER playlist -> 2 variants, bandwidths, is_master
160 let m2: *u8 = "#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1280000,RESOLUTION=720x480\n720p.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=2560000,RESOLUTION=1080x720\n1080p.m3u8\n\x00" as *u8
161 let c2: i64 = nx_hls_parse(m2, hp_slen(m2), out, 256, meta)
162 var r2: i64 = 1
163 if c2 != 2 { r2 = 0 }
164 if meta[0] != 1 { r2 = 0 } // master
165 if r2 == 1 { if hp_uri_eq(m2, out, 0, "720p.m3u8\x00" as *u8) == 0 { r2 = 0 } }
166 if r2 == 1 { if out[0*3+2] != 1280000 { r2 = 0 } }
167 if r2 == 1 { if hp_uri_eq(m2, out, 1, "1080p.m3u8\x00" as *u8) == 0 { r2 = 0 } }
168 if r2 == 1 { if out[1*3+2] != 2560000 { r2 = 0 } }
169 if r2 == 1 { hp_puts(" PASS KAT2 master playlist: 2 variants + bandwidths\n" as *u8); pass=pass+1 } else { hp_puts(" FAIL KAT2 master (count=" as *u8); hp_putn(c2); hp_puts(")\n" as *u8) }
170 tot = tot + 1
171
172 // KAT3 (TAMPER): non-m3u8 text -> -1, never a fabricated parse
173 let m3: *u8 = "<html>not a playlist</html>\nseg.ts\n\x00" as *u8
174 let c3: i64 = nx_hls_parse(m3, hp_slen(m3), out, 256, meta)
175 var r3: i64 = 1
176 if c3 != (0 - 1) { r3 = 0 }
177 if r3 == 1 { hp_puts(" PASS KAT3 tamper: non-m3u8 -> -1 (not faked)\n" as *u8); pass=pass+1 } else { hp_puts(" FAIL KAT3 tamper (got=" as *u8); hp_putn(c3); hp_puts(")\n" as *u8) }
178 tot = tot + 1
179
180 hp_puts("---- nx_hls_parse gate: passed " as *u8); hp_putn(pass); hp_puts(" / " as *u8); hp_putn(tot); hp_puts("\n" as *u8)
181 if pass == tot { sys_exit(0); return 0 }
182 sys_exit(1)
183 return 1
184}