code wiki / (root) / nx_torrent_peer_dl.nx

nx_torrent_peer_dl.nx source

↩ module page · 225 lines · 13573 B

1// nx_torrent_peer_dl.nx -- ORCHESTRATION (X-TORRENT-LIVE-001): a single-peer FULL-FILE download 2// composing the proven mechanisms into one flow -> writes the complete, verified file to disk. 3// From info_hash + a connected peer: BEP-3 handshake(ext) -> BEP-10 ext handshake -> BEP-9 4// ut_metadata fetch (= the piece map: num_pieces, piece_length, total, per-piece sha1 hashes) -> 5// interested/unchoke -> for each data piece: BEP-3 request -> PIECE -> sha1(piece)==pieces[i] (REAL 6// per-piece verify) -> write at offset i*piece_length -> the complete file. Reuses nx_peerwire + 7// nx_bencode + nx_sha1. GATE = SOVEREIGN LOOPBACK: a mock peer serves a small 2-piece test file 8// (48 bytes, piece_length 32) with the REAL piece hashes in its info-dict; the client downloads the 9// whole thing and the on-disk file must equal the original. license_tier: ORIGINAL 10// 11// module: nishi-core.torrent.peer_dl 12// depends: nishi-core.torrent.peerwire, nishi-core.torrent.bencode, nishi-core.crypto.sha1 13import "nx_peerwire.nx" 14import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 15import "nx_bencode.nx" 16import "nx_sha1.nx" 17const PW_MAGIC_8192: i64 = 8192 18const PW_MAGIC_53491: i64 = 53491 19const PW_MAGIC_2048: i64 = 2048 20const PW_MAGIC_1024: i64 = 1024 21 22const PW_EXTENDED: i64 = 20 23const TOTAL: i64 = 48 24const PLEN: i64 = 32 // piece length -> pieces: 0=[0,32) 1=[32,48) 25const NPIECES: i64 = 2 26 27func td_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 } 28func td_wn(fd: i64, v: i64) -> i64 { 29 let t: *u8=sys_mmap(28); var m: i64=v; if m<0 {m=0-m; sys_write(fd,"-" as *u8,1)} 30 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} 31 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 32} 33func td_bstr(buf: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8) { buf[off+i]=s[i]; i=i+1 } return off+i } 34func td_bint(buf: *u8, off: i64, v: i64) -> i64 { 35 let t: *u8=sys_mmap(28); var m: i64=v; 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} 36 var o: i64=off; var j: i64=0; while j<k { buf[o]=t[k-1-j]; o=o+1; j=j+1 } return o 37} 38func td_sockaddr(sa: *u8, port: i64, i0: i64, i1: i64, i2: i64, i3: i64) -> i64 { 39 sa[0]=2 as u8; sa[1]=0 as u8 40 sa[2]=((port>>8)&0xff) as u8; sa[3]=(port&0xff) as u8 41 sa[4]=i0 as u8; sa[5]=i1 as u8; sa[6]=i2 as u8; sa[7]=i3 as u8 42 var k: i64=8; while k<16 { sa[k]=0 as u8; k=k+1 } 43 return 0 44} 45func td_read_n(fd: i64, buf: *u8, n: i64) -> i64 { 46 var got: i64 = 0 47 while got < n { let r: i64 = sys_read(fd, buf + got, n - got); if r <= 0 { return 0 - 1 } got = got + r } 48 return got 49} 50func td_write_n(fd: i64, buf: *u8, n: i64) -> i64 { 51 var off: i64 = 0 52 while off < n { let w: i64 = sys_write(fd, buf + off, n - off); if w <= 0 { return 0 - 1 } off = off + w } 53 return n 54} 55func td_read_msg(fd: i64, mb: *u8, cap: i64) -> i64 { 56 if td_read_n(fd, mb, 4) != 4 { return 0 - 1 } 57 let blen: i64 = _pw_get_u32(mb, 0) 58 if blen == 0 { return 0 } 59 if blen > cap - 4 { return 0 - 1 } 60 if td_read_n(fd, mb + 4, blen) != blen { return 0 - 1 } 61 return blen 62} 63func td_build_ext(ext_id: i64, data: *u8, dlen: i64, out: *u8) -> i64 { 64 let pl: *u8 = sys_mmap(PW_MAGIC_8192) 65 pl[0] = ext_id as u8 66 var i: i64 = 0; while i < dlen { pl[1 + i] = data[i]; i = i + 1 } 67 return nx_pw_build_msg(PW_EXTENDED, pl, 1 + dlen, out) 68} 69func td_eq(a: *u8, b: *u8, n: i64) -> i64 { 70 var i: i64 = 0 71 while i < n { if a[i] != b[i] { return 0 } i = i + 1 } 72 return 1 73} 74 75func main() -> i64 { 76 let PORT: i64 = PW_MAGIC_53491 77 // the test file: 48 bytes [0,1,2,...,47] 78 let file: *u8 = sys_mmap(TOTAL); var i: i64 = 0; while i < TOTAL { file[i] = i as u8; i = i + 1 } 79 // per-piece sha1 hashes (real) -> the info-dict "pieces" (40 bytes) 80 let pieces: *u8 = sys_mmap(40) 81 sha1(file, PLEN, pieces) // hash piece 0 = [0,32) 82 sha1((file as i64 + PLEN) as *u8, TOTAL - PLEN, (pieces as i64 + 20) as *u8) // hash piece 1 = [32,48) 83 // info-dict (sorted keys): length, name, piece length, pieces 84 let idd: *u8 = sys_mmap(256) 85 var p: i64 = td_bstr(idd, 0, "d6:lengthi48e4:name10:test48.bin12:piece lengthi32e6:pieces40:" as *u8) 86 var j: i64 = 0; while j < 40 { idd[p] = pieces[j]; p = p + 1; j = j + 1 } 87 idd[p] = 101 as u8; p = p + 1 // 'e' 88 let N: i64 = p 89 let info_hash: *u8 = sys_mmap(20); sha1(idd, N, info_hash) 90 let pidb: *u8 = sys_mmap(20); var z: i64 = 0; while z < 20 { pidb[z] = 80 as u8; z = z + 1 } 91 let sa: *u8 = sys_mmap(16); td_sockaddr(sa, PORT, 127, 0, 0, 1) 92 let dlpath: *u8 = "/tmp/_nx_torrent_dl.bin" as *u8 93 94 let lfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 95 var bound: i64 = 0 96 if lfd >= 0 { 97 let one: *u8 = sys_mmap(4); one[0]=1 as u8; one[1]=0 as u8; one[2]=0 as u8; one[3]=0 as u8 98 sys_setsockopt(lfd, SOL_SOCKET, 2, one, 4) 99 sys_set_socket_timeout(lfd, 4) 100 if sys_bind(lfd, sa, 16) >= 0 { if sys_listen(lfd, 1) >= 0 { bound = 1 } } 101 } 102 if bound == 0 { td_w(1, "TPDL-GATE authored=organ bound=0 verdict=RED\n" as *u8); sys_exit(1); return 1 } 103 104 let pid: i64 = sys_fork() 105 if pid == 0 { 106 // ===== MOCK PEER (lockstep) ===== 107 let afd: i64 = sys_accept(lfd) 108 if afd >= 0 { 109 sys_set_socket_timeout(afd, 4) 110 let hb: *u8 = sys_mmap(128); td_read_n(afd, hb, 68) 111 let oh: *u8 = sys_mmap(128); nx_pw_build_handshake(info_hash, pidb, oh); oh[25] = (oh[25] as i64 | 0x10) as u8; td_write_n(afd, oh, 68) 112 let mb: *u8 = sys_mmap(PW_MAGIC_2048) 113 td_read_msg(afd, mb, PW_MAGIC_2048) // client ext handshake 114 let eb: *u8 = sys_mmap(256) 115 var o: i64 = td_bstr(eb, 0, "d1:md11:ut_metadatai2ee13:metadata_sizei" as *u8); o = td_bint(eb, o, N); o = td_bstr(eb, o, "ee" as *u8) 116 let om: *u8 = sys_mmap(512); td_write_n(afd, om, td_build_ext(0, eb, o, om)) 117 td_read_msg(afd, mb, PW_MAGIC_2048) // metadata request 118 let db: *u8 = sys_mmap(PW_MAGIC_1024) 119 var d: i64 = td_bstr(db, 0, "d8:msg_typei1e5:piecei0e10:total_sizei" as *u8); d = td_bint(db, d, N); d = td_bstr(db, d, "ee" as *u8) 120 var q: i64 = 0; while q < N { db[d + q] = idd[q]; q = q + 1 } 121 let dm: *u8 = sys_mmap(PW_MAGIC_2048); td_write_n(afd, dm, td_build_ext(1, db, d + N, dm)) 122 td_read_msg(afd, mb, PW_MAGIC_2048) // INTERESTED 123 let un: *u8 = sys_mmap(16); td_write_n(afd, un, nx_pw_build_msg(NX_PW_UNCHOKE, 0 as *u8, 0, un)) 124 var pi: i64 = 0 125 while pi < NPIECES { 126 td_read_msg(afd, mb, PW_MAGIC_2048) // REQUEST(index,begin,length) 127 let idx: i64 = _pw_get_u32(mb, 5) 128 let beg: i64 = _pw_get_u32(mb, 9) 129 let length: i64 = _pw_get_u32(mb, 13) 130 let pl: *u8 = sys_mmap(256); _pw_put_u32(pl, 0, idx); _pw_put_u32(pl, 4, beg) 131 var x: i64 = 0; while x < length { pl[8 + x] = file[idx*PLEN + beg + x]; x = x + 1 } 132 let pm: *u8 = sys_mmap(512); td_write_n(afd, pm, nx_pw_build_msg(NX_PW_PIECE, pl, 8 + length, pm)) 133 pi = pi + 1 134 } 135 sys_close(afd) 136 } 137 sys_close(lfd) 138 sys_exit(0) 139 } 140 141 // ===== CLIENT (orchestrator) ===== 142 let cfd: i64 = sys_socket(AF_INET, SOCK_STREAM, 0) 143 sys_set_socket_timeout(cfd, 4) 144 var ok_conn: i64 = 0; var ok_meta: i64 = 0; var ok_pieces: i64 = 0; var ok_disk: i64 = 0 145 var got_np: i64 = 0; var got_plen: i64 = 0; var got_total: i64 = 0 146 let dl: *u8 = sys_mmap(TOTAL + 64) 147 if nx_connect_bounded(cfd, sa, 16, NX_CONN_DEFAULT_MS) >= 0 { 148 ok_conn = 1 149 let ch: *u8 = sys_mmap(128); nx_pw_build_handshake(info_hash, pidb, ch); ch[25] = (ch[25] as i64 | 0x10) as u8; td_write_n(cfd, ch, 68) 150 let ph2: *u8 = sys_mmap(128); td_read_n(cfd, ph2, 68) 151 // ext handshake 152 let ceb: *u8 = sys_mmap(64); let cm: *u8 = sys_mmap(128); td_write_n(cfd, cm, td_build_ext(0, ceb, td_bstr(ceb, 0, "d1:md11:ut_metadatai1eee" as *u8), cm)) 153 let mb: *u8 = sys_mmap(PW_MAGIC_2048); let bl: i64 = td_read_msg(cfd, mb, PW_MAGIC_2048) 154 var utm_id: i64 = 0 155 if bl > 2 { let nt: i64 = 4 + bl; let mo: i64 = nx_bc_dict_get(mb, 6, nt, "m" as *u8, 1) 156 if mo >= 0 { let uo: i64 = nx_bc_dict_get(mb, mo, nt, "ut_metadata" as *u8, 11); if uo >= 0 { let iv: *i64 = sys_mmap(16) as *i64; if nx_bc_int(mb, uo, nt, iv) >= 0 { utm_id = iv[0] } } } } 157 if utm_id > 0 { 158 // fetch metadata 159 let rqb: *u8 = sys_mmap(64); let rm: *u8 = sys_mmap(128); td_write_n(cfd, rm, td_build_ext(utm_id, rqb, td_bstr(rqb, 0, "d8:msg_typei0e5:piecei0ee" as *u8), rm)) 160 let db2: *u8 = sys_mmap(PW_MAGIC_2048); let dbl: i64 = td_read_msg(cfd, db2, PW_MAGIC_2048) 161 if dbl > 2 { let dt: i64 = 4 + dbl; let ro: i64 = nx_bc_skip(db2, 6, dt) 162 if ro > 0 { 163 let meta: *u8 = sys_mmap(512); let mlen: i64 = dt - ro; var c: i64 = 0; while c < mlen { meta[c] = db2[ro + c]; c = c + 1 } 164 let gh: *u8 = sys_mmap(20); sha1(meta, mlen, gh) 165 if td_eq(gh, info_hash, 20) == 1 { 166 // parse piece map 167 let plo: i64 = nx_bc_dict_get(meta, 0, mlen, "piece length" as *u8, 12); let iv2: *i64 = sys_mmap(16) as *i64 168 if plo >= 0 { nx_bc_int(meta, plo, mlen, iv2); got_plen = iv2[0] } 169 let lno: i64 = nx_bc_dict_get(meta, 0, mlen, "length" as *u8, 6) 170 if lno >= 0 { nx_bc_int(meta, lno, mlen, iv2); got_total = iv2[0] } 171 let pco: i64 = nx_bc_dict_get(meta, 0, mlen, "pieces" as *u8, 6) 172 let pso: *i64 = sys_mmap(16) as *i64; let psl: *i64 = sys_mmap(16) as *i64 173 if pco >= 0 { nx_bc_str(meta, pco, mlen, pso, psl) } 174 if got_plen > 0 { got_np = (got_total + got_plen - 1) / got_plen; ok_meta = 1 } 175 if ok_meta == 1 { 176 // interested -> unchoke 177 let im: *u8 = sys_mmap(16); td_write_n(cfd, im, nx_pw_build_msg(NX_PW_INTERESTED, 0 as *u8, 0, im)) 178 td_read_msg(cfd, mb, PW_MAGIC_2048) // UNCHOKE 179 var allp: i64 = 1 180 var pidx: i64 = 0 181 while pidx < got_np { 182 var plen_i: i64 = got_plen 183 if pidx == got_np - 1 { plen_i = got_total - pidx*got_plen } 184 let rq: *u8 = sys_mmap(32); td_write_n(cfd, rq, nx_pw_build_request(pidx, 0, plen_i, rq)) 185 let pb: *u8 = sys_mmap(PW_MAGIC_2048); let pl2: i64 = td_read_msg(cfd, pb, PW_MAGIC_2048) 186 if pl2 >= 9 { if nx_pw_msg_id(pb) == NX_PW_PIECE { 187 // block at pb[13..], verify piece sha1 vs pieces[pidx] 188 let blk: *u8 = (pb as i64 + 13) as *u8 189 let ph: *u8 = sys_mmap(20); sha1(blk, plen_i, ph) 190 let exp: *u8 = (meta as i64 + pso[0] + pidx*20) as *u8 191 if td_eq(ph, exp, 20) == 1 { 192 var y: i64 = 0; while y < plen_i { dl[pidx*got_plen + y] = blk[y]; y = y + 1 } 193 } else { allp = 0 } 194 } else { allp = 0 } } else { allp = 0 } 195 pidx = pidx + 1 196 } 197 ok_pieces = allp 198 if ok_pieces == 1 { 199 // WRITE the complete file + read back 200 let wf: i64 = sys_openat_wr(dlpath, 0x1a4); if wf >= 0 { td_write_n(wf, dl, got_total); sys_close(wf) } 201 let rf: i64 = sys_openat_rd(dlpath) 202 if rf >= 0 { let disk: *u8 = sys_mmap(TOTAL + 64); let dn: i64 = td_read_n(rf, disk, got_total); sys_close(rf) 203 if dn == got_total { if td_eq(disk, file, got_total) == 1 { ok_disk = 1 } } } 204 } 205 } 206 } 207 } 208 } 209 } 210 } 211 let st: *i64 = sys_mmap(16) as *i64; sys_wait4(pid, st, 0); sys_close(cfd); sys_close(lfd) 212 213 td_w(1, "TPDL-GATE authored=organ conn=" as *u8); td_wn(1, ok_conn) 214 td_w(1, " meta=" as *u8); td_wn(1, ok_meta) 215 td_w(1, " npieces=" as *u8); td_wn(1, got_np) 216 td_w(1, " plen=" as *u8); td_wn(1, got_plen) 217 td_w(1, " total=" as *u8); td_wn(1, got_total) 218 td_w(1, " pieces_verified=" as *u8); td_wn(1, ok_pieces) 219 td_w(1, " disk_eq_file=" as *u8); td_wn(1, ok_disk) 220 var allok: i64 = 0 221 if ok_conn==1 { if ok_meta==1 { if ok_pieces==1 { if ok_disk==1 { allok = 1 } } } } 222 if allok == 1 { td_w(1, " verdict=GREEN file=/tmp/_nx_torrent_dl.bin\n" as *u8); sys_exit(0); return 0 } 223 td_w(1, " verdict=RED\n" as *u8); sys_exit(1) 224 return 1 225}