code wiki / (root) / nx_magnet_owned_t271.nx

nx_magnet_owned_t271.nx source

↩ module page · 304 lines · 11996 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} 218 219const MG_BTIH_BYTES:i64=20 220struct MgOwned { 221 storage:*u8, 222 storage_bytes:i64, 223 info_hash:*u8, 224 trackers:*i64, 225 tracker_count:i64, 226 name:*u8, 227} 228func mg_owned_clear(o:*MgOwned)->i64{ 229 o.storage=0 as *u8;o.storage_bytes=0;o.info_hash=0 as *u8 230 o.trackers=0 as *i64;o.tracker_count=0;o.name=0 as *u8 231 return 0 232} 233func mg_owned_release(o:*MgOwned)->i64{ 234 var rc:i64=0 235 if (o.storage as i64)>0{rc=sys_munmap(o.storage,o.storage_bytes)} 236 mg_owned_clear(o);return rc 237} 238// Explicit extent is the boundary contract; zero bytes and malformed escapes 239// are rejected instead of becoming truncated downstream C strings. 240func mg_decode_text(src:*u8,a:i64,b:i64,out:*u8)->i64{ 241 var i:i64=a;var n:i64=0 242 while i<b{ 243 var c:i64=src[i] as i64 244 if c==37{ 245 if b-i<3{return -1} 246 let hi:i64=mg_hexval(src[i+1] as i64);let lo:i64=mg_hexval(src[i+2] as i64) 247 if hi<0||lo<0{return -1};c=hi*16+lo;i=i+3 248 }else{if c==43{c=32};i=i+1} 249 if c==0{return -1};out[n]=c as u8;n=n+1 250 };out[n]=0 as u8;return n 251} 252// Output must be empty, or released before reuse. The one owned mapping 253// contains identity, the complete tracker pointer table, name and decoded text. 254// Returns 1 parsed, 0 invalid, -1 allocation/extent failure. 255func mg_parse_owned(uri:*u8,n:i64,o:*MgOwned)->i64{ 256 mg_owned_clear(o) 257 if n<=0{return 0} 258 if mg_starts(uri,0,n,"magnet:?" as *u8)!=1{return 0} 259 var p:i64=0;while p<n{if uri[p]==0 as u8{return 0};p=p+1} 260 var count:i64=0;p=mg_len("magnet:?" as *u8) 261 while p<n{ 262 var e:i64=p;while e<n{if uri[e]==38 as u8{break};e=e+1} 263 var eq:i64=p;while eq<e{if uri[eq]==61 as u8{break};eq=eq+1} 264 if eq<e{if mg_keyeq(uri,p,eq,"tr" as *u8)==1{count=count+1}} 265 p=e+1 266 } 267 let slot:i64=__size_of(i64) 268 if n> (9223372036854775807-MG_BTIH_BYTES-slot)/2-1{return -1} 269 let texts:i64=(n+1)*2 270 if count>(9223372036854775807-texts-MG_BTIH_BYTES)/slot{return -1} 271 let bytes:i64=count*slot+texts+MG_BTIH_BYTES 272 let mem:*u8=sys_mmap_try(bytes) 273 if (mem as i64)<=0{return -1} 274 o.storage=mem;o.storage_bytes=bytes;o.trackers=mem as *i64 275 o.info_hash=mem+count*slot;o.name=o.info_hash+MG_BTIH_BYTES 276 var next:*u8=o.name+n+1 277 var found:i64=0;p=mg_len("magnet:?" as *u8) 278 while p<n{ 279 var e:i64=p;while e<n{if uri[e]==38 as u8{break};e=e+1} 280 var eq:i64=p;while eq<e{if uri[eq]==61 as u8{break};eq=eq+1} 281 if eq<e{ 282 let v:i64=eq+1 283 if mg_keyeq(uri,p,eq,"xt" as *u8)==1{ 284 if mg_starts(uri,v,e,"urn:btih:" as *u8)==1{ 285 // Multiple btih identities are refused instead of silently choosing one. 286 if found==1{mg_owned_release(o);return 0} 287 if mg_decode_btih(uri,v+mg_len("urn:btih:" as *u8),e,o.info_hash)!=MG_BTIH_BYTES{mg_owned_release(o);return 0} 288 found=1 289 } 290 } 291 if mg_keyeq(uri,p,eq,"dn" as *u8)==1{ 292 if mg_decode_text(uri,v,e,o.name)<0{mg_owned_release(o);return 0} 293 } 294 if mg_keyeq(uri,p,eq,"tr" as *u8)==1{ 295 let len:i64=mg_decode_text(uri,v,e,next) 296 if len<0{mg_owned_release(o);return 0} 297 o.trackers[o.tracker_count]=next as i64;o.tracker_count=o.tracker_count+1 298 next=next+len+1 299 } 300 };p=e+1 301 } 302 if found!=1{mg_owned_release(o);return 0} 303 return 1 304}