code wiki / (root) / nx_magnet.nx

nx_magnet.nx source

↩ module page · 217 lines · 8922 B

1// nx_magnet.nx -- BEP-9 magnet-URI parser (the live-client ENTRY point: add_magnet's first step, 2// X-TORRENT-LIVE-001). Pure/deterministic, no network: parse "magnet:?xt=urn:btih:<hash>&dn=..&tr=.." 3// into the 20-byte info_hash + tracker URL list + display name. The info_hash is the identity used 4// by tracker announces (nx_udp_tracker) + peer handshakes (BEP-3); trackers seed peer discovery; 5// the name is the NAS target. Handles BOTH btih forms: 40-hex and 32-char base32 (RFC4648), and 6// %XX / '+' URL-decoding of tr/dn. Sovereign (raw syscalls only); BitTorrent magnet = last-mile 7// interop, the parse is Nishi-native. Self-gating (main = baked KAT). license_tier: ORIGINAL 8// 9// module: nishi-core.torrent.magnet 10// depends: nishi-core.sys.syscalls 11// capability: TORRENT_MAGNET_PARSE 12import "nx_syscalls.nx" 13const MG_MAGIC_1024: i64 = 1024 14 15const MG_NTRK_MAX: i64 = 32 16 17func mg_w(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 18func mg_wn(fd: i64, v: i64) -> i64 { 19 let t: *u8=sys_mmap(28); var m: i64=v; if m<0 {m=0-m; sys_write(fd,"-" as *u8,1)} 20 var k: i64=0; if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1} 21 let b: *u8=sys_mmap(28); var i: i64=0; while i<k {b[i]=t[k-1-i]; i=i+1}; sys_write(fd,b,k); return 0 22} 23 24func mg_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 25 26// hex char -> 0..15, else -1 27func mg_hexval(c: i64) -> i64 { 28 if c >= 48 { if c <= 57 { return c - 48 } } // 0-9 29 if c >= 97 { if c <= 102 { return c - 97 + 10 } } // a-f 30 if c >= 65 { if c <= 70 { return c - 65 + 10 } } // A-F 31 return 0 - 1 32} 33// base32 char (RFC4648) -> 0..31, else -1 34func mg_b32val(c: i64) -> i64 { 35 if c >= 65 { if c <= 90 { return c - 65 } } // A-Z = 0..25 36 if c >= 97 { if c <= 122 { return c - 97 } } // a-z = 0..25 (case-insensitive) 37 if c >= 50 { if c <= 55 { return c - 50 + 26 } } // 2-7 = 26..31 38 return 0 - 1 39} 40 41// decode the btih value buf[a,b) (40-hex OR 32-base32) into out[0..20). returns 20 ok / 0 fail. 42func mg_decode_btih(buf: *u8, a: i64, b: i64, out: *u8) -> i64 { 43 let n: i64 = b - a 44 if n == 40 { 45 var i: i64 = 0 46 while i < 20 { 47 let hi: i64 = mg_hexval(buf[a + 2*i] as i64) 48 let lo: i64 = mg_hexval(buf[a + 2*i + 1] as i64) 49 if hi < 0 { return 0 } 50 if lo < 0 { return 0 } 51 out[i] = (hi * 16 + lo) as u8 52 i = i + 1 53 } 54 return 20 55 } 56 if n == 32 { 57 var acc: i64 = 0 58 var bits: i64 = 0 59 var oi: i64 = 0 60 var i: i64 = 0 61 while i < 32 { 62 let v: i64 = mg_b32val(buf[a + i] as i64) 63 if v < 0 { return 0 } 64 acc = acc * 32 + v 65 bits = bits + 5 66 if bits >= 8 { 67 bits = bits - 8 68 // emit the top byte: (acc >> bits) & 0xff 69 var sh: i64 = acc 70 var s: i64 = 0 71 while s < bits { sh = sh / 2; s = s + 1 } 72 out[oi] = (sh % 256) as u8 73 oi = oi + 1 74 // drop the emitted top bits from acc: acc = acc % (1<<bits) 75 var modv: i64 = 1 76 var s2: i64 = 0 77 while s2 < bits { modv = modv * 2; s2 = s2 + 1 } 78 acc = acc % modv 79 } 80 i = i + 1 81 } 82 if oi == 20 { return 20 } 83 return 0 84 } 85 return 0 86} 87 88// URL-decode src[a,b) into out (null-terminated): %XX -> byte, '+' -> space, else copy. 89func mg_urldecode(src: *u8, a: i64, b: i64, out: *u8) -> i64 { 90 var i: i64 = a 91 var o: i64 = 0 92 while i < b { 93 let c: i64 = src[i] as i64 94 if c == 37 { // '%' 95 if i + 2 < b { 96 let hi: i64 = mg_hexval(src[i+1] as i64) 97 let lo: i64 = mg_hexval(src[i+2] as i64) 98 if hi >= 0 { if lo >= 0 { out[o] = (hi*16+lo) as u8; o = o + 1; i = i + 3 } else { out[o]=src[i] as u8; o=o+1; i=i+1 } } else { out[o]=src[i] as u8; o=o+1; i=i+1 } 99 } else { out[o] = src[i] as u8; o = o + 1; i = i + 1 } 100 } else { 101 if c == 43 { out[o] = 32 as u8 } else { out[o] = src[i] as u8 } // '+' -> space 102 o = o + 1; i = i + 1 103 } 104 } 105 out[o] = 0 as u8 106 return o 107} 108 109// key buf[a,kb) equals lit? (lit null-terminated) 110func mg_keyeq(buf: *u8, a: i64, kb: i64, lit: *u8) -> i64 { 111 let ll: i64 = mg_len(lit) 112 if kb - a != ll { return 0 } 113 var i: i64 = 0 114 while i < ll { if buf[a+i] != lit[i] { return 0 } i = i + 1 } 115 return 1 116} 117 118// buf[a,..) starts with lit? 1/0 119func mg_starts(buf: *u8, a: i64, b: i64, lit: *u8) -> i64 { 120 let ll: i64 = mg_len(lit) 121 if a + ll > b { return 0 } 122 var i: i64 = 0 123 while i < ll { if buf[a+i] != lit[i] { return 0 } i = i + 1 } 124 return 1 125} 126 127// parse a magnet URI. ihash=20B out; trk=ptr array (trkcap); ntrk[0]=tracker count out; name=out. 128// returns 1 iff a valid btih info_hash was decoded, else 0. 129func mg_parse(uri: *u8, ihash: *u8, trk: *i64, trkcap: i64, ntrk: *i64, name: *u8) -> i64 { 130 ntrk[0] = 0 131 name[0] = 0 as u8 132 let n: i64 = mg_len(uri) 133 if mg_starts(uri, 0, n, "magnet:?" as *u8) == 0 { return 0 } 134 var found: i64 = 0 135 var p: i64 = 8 // after "magnet:?" 136 while p < n { 137 // param = [p, e) where e = next '&' or n 138 var e: i64 = p 139 var g: i64 = 1 140 while g == 1 { if e >= n { g = 0 } else { if uri[e] == (38 as u8) { g = 0 } else { e = e + 1 } } } 141 // '=' splits key=[p,eq) value=[eq+1,e) 142 var eq: i64 = p 143 var ge: i64 = 1 144 while ge == 1 { if eq >= e { ge = 0 } else { if uri[eq] == (61 as u8) { ge = 0 } else { eq = eq + 1 } } } 145 if eq < e { 146 let vstart: i64 = eq + 1 147 if mg_keyeq(uri, p, eq, "xt" as *u8) == 1 { 148 if mg_starts(uri, vstart, e, "urn:btih:" as *u8) == 1 { 149 if mg_decode_btih(uri, vstart + 9, e, ihash) == 20 { found = 1 } 150 } 151 } 152 if mg_keyeq(uri, p, eq, "tr" as *u8) == 1 { 153 if ntrk[0] < trkcap { 154 let tb: *u8 = sys_mmap(MG_MAGIC_1024) 155 mg_urldecode(uri, vstart, e, tb) 156 trk[ntrk[0]] = tb as i64 157 ntrk[0] = ntrk[0] + 1 158 } 159 } 160 if mg_keyeq(uri, p, eq, "dn" as *u8) == 1 { 161 mg_urldecode(uri, vstart, e, name) 162 } 163 } 164 p = e + 1 165 } 166 return found 167} 168 169// ===================== BAKED SELF-TEST (KAT) ===================== 170func main() -> i64 { 171 let ihash: *u8 = sys_mmap(32) 172 let trk: *i64 = sys_mmap(8 * MG_NTRK_MAX) as *i64 173 let ntrk: *i64 = sys_mmap(16) as *i64 174 let name: *u8 = sys_mmap(MG_MAGIC_1024) 175 176 // ---- pos1: 40-hex btih + url-encoded trackers + '+'-encoded name ---- 177 let u1: *u8 = "magnet:?dn=Test+File&xt=urn:btih:0123456789ABCDEF0123456789ABCDEF01234567&tr=http%3A%2F%2Ftracker.example%2Fannounce&tr=udp%3A%2F%2Ftr2%3A80" as *u8 178 let r1: i64 = mg_parse(u1, ihash, trk, MG_NTRK_MAX, ntrk, name) 179 var pos1: i64 = 0 180 if r1 == 1 { 181 if (ihash[0] as i64) == 1 { if (ihash[1] as i64) == 35 { if (ihash[19] as i64) == 103 { 182 if ntrk[0] == 2 { 183 let t0: *u8 = trk[0] as *u8 184 if mg_starts(t0, 0, mg_len(t0), "http://tracker.example/announce" as *u8) == 1 { 185 // name == "Test File" 186 if name[0]==(84 as u8) { if name[4]==(32 as u8) { if name[5]==(70 as u8) { pos1 = 1 } } } 187 } 188 } 189 } } } 190 } 191 192 // ---- pos2: 32-char base32 btih, all-'A' -> 20 zero bytes ---- 193 let u2: *u8 = "magnet:?xt=urn:btih:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" as *u8 194 let r2: i64 = mg_parse(u2, ihash, trk, MG_NTRK_MAX, ntrk, name) 195 var pos2: i64 = 0 196 if r2 == 1 { if (ihash[0] as i64)==0 { if (ihash[10] as i64)==0 { if (ihash[19] as i64)==0 { pos2 = 1 } } } } 197 198 // ---- neg1: non-btih xt -> reject ---- 199 let u3: *u8 = "magnet:?xt=urn:sha1:HELLO&dn=x" as *u8 200 var neg1: i64 = 0 201 if mg_parse(u3, ihash, trk, MG_NTRK_MAX, ntrk, name) == 0 { neg1 = 1 } 202 203 // ---- neg2: btih wrong length (4 hex) -> reject ---- 204 let u4: *u8 = "magnet:?xt=urn:btih:0123" as *u8 205 var neg2: i64 = 0 206 if mg_parse(u4, ihash, trk, MG_NTRK_MAX, ntrk, name) == 0 { neg2 = 1 } 207 208 mg_w(1, "MAGNET-GATE authored=organ pos1_hex=" as *u8); mg_wn(1, pos1) 209 mg_w(1, " pos2_base32=" as *u8); mg_wn(1, pos2) 210 mg_w(1, " neg1_nonbtih=" as *u8); mg_wn(1, neg1) 211 mg_w(1, " neg2_badlen=" as *u8); mg_wn(1, neg2) 212 var ok: i64 = 0 213 if pos1 == 1 { if pos2 == 1 { if neg1 == 1 { if neg2 == 1 { ok = 1 } } } } 214 if ok == 1 { mg_w(1, " verdict=GREEN\n" as *u8); sys_exit(0); return 0 } 215 mg_w(1, " verdict=RED\n" as *u8); sys_exit(1) 216 return 1 217}