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}