nx_tracker.nx source
↩ module page · 108 lines · 4094 B
1// nx_tracker.nx -- BitTorrent HTTP tracker announce (bits-up), reuses the net
2// primitives.
3//
4// module: nishi-core.torrent.tracker
5// depends: nx_str.nx, nx_syscalls.nx, nx_bencode.nx
6// capability: CORE_COMPUTE
7// wired_status: FULLY_WIRED
8//
9// Build the announce request (%-encoded info_hash + peer_id + numeric params,
10// BEP-3) and parse the compact peer list (BEP-23: peers = 6N bytes, 4 IP + 2
11// port BE). The LIVE announce reuses nx_http_get_ua (the crawler's UA HTTP GET)
12// + nx_net_governor (ISP-safe pacing) + DNS/TLS -- the reusability payoff: the
13// torrent client's tracker step IS the search engine's net stack. Pure builder
14// + parser here (KAT'd); the socket round-trip is the shared primitive.
15
16import "nx_str.nx"
17import "nx_syscalls.nx"
18import "nx_bencode.nx"
19
20func _trk_hex(nib: i64) -> i64 {
21 if nib < 10 { return 0x30 + nib }
22 return 0x61 + (nib - 10) // 'a'..'f'
23}
24// RFC 3986 unreserved: A-Z a-z 0-9 - _ . ~
25func _trk_unreserved(c: i64) -> i64 {
26 if c >= 0x41 { if c <= 0x5A { return 1 } }
27 if c >= 0x61 { if c <= 0x7A { return 1 } }
28 if c >= 0x30 { if c <= 0x39 { return 1 } }
29 if c == 0x2D { return 1 }
30 if c == 0x5F { return 1 }
31 if c == 0x2E { return 1 }
32 if c == 0x7E { return 1 }
33 return 0
34}
35// %-encode `n` raw bytes into out; returns chars written.
36func nx_trk_pct_encode(bytes: *u8, n: i64, out: *u8) -> i64 {
37 var o: i64 = 0
38 var i: i64 = 0
39 while i < n {
40 let c: i64 = bytes[i] as i64
41 if _trk_unreserved(c) == 1 { out[o] = c; o = o + 1 }
42 else {
43 out[o] = 0x25; out[o + 1] = _trk_hex((c >> 4) & 0xF); out[o + 2] = _trk_hex(c & 0xF)
44 o = o + 3
45 }
46 i = i + 1
47 }
48 return o
49}
50
51func _trk_lit(out: *u8, o: i64, s: *u8) -> i64 {
52 let n: i64 = nx_str_len(s); var i: i64 = 0
53 while i < n { out[o + i] = s[i]; i = i + 1 }
54 return o + n
55}
56func _trk_dec(out: *u8, o: i64, v: i64) -> i64 {
57 if v == 0 { out[o] = 0x30; return o + 1 }
58 var x: i64 = v; let tmp: *u8 = sys_mmap(32); var k: i64 = 0
59 while x > 0 { tmp[k] = 0x30 + (x - (x / 10) * 10); x = x / 10; k = k + 1 }
60 var oo: i64 = o
61 while k > 0 { k = k - 1; out[oo] = tmp[k]; oo = oo + 1 }
62 return oo
63}
64
65// Build the announce request PATH+QUERY into out (null-terminated). Returns len.
66// The host is resolved + fetched by the shared nx_http_get_ua.
67func nx_tracker_build_path(announce_path: *u8, aplen: i64,
68 info_hash: *u8, peer_id: *u8, port: i64, left: i64,
69 out: *u8) -> i64 {
70 var o: i64 = 0
71 var i: i64 = 0
72 while i < aplen { out[o] = announce_path[i]; o = o + 1; i = i + 1 }
73 o = _trk_lit(out, o, "?info_hash=")
74 o = o + nx_trk_pct_encode(info_hash, 20, ((out as i64) + o) as *u8)
75 o = _trk_lit(out, o, "&peer_id=")
76 o = o + nx_trk_pct_encode(peer_id, 20, ((out as i64) + o) as *u8)
77 o = _trk_lit(out, o, "&port=")
78 o = _trk_dec(out, o, port)
79 o = _trk_lit(out, o, "&uploaded=0&downloaded=0&left=")
80 o = _trk_dec(out, o, left)
81 o = _trk_lit(out, o, "&compact=1")
82 out[o] = 0
83 return o
84}
85
86// Parse a tracker response: compact peers (BEP-23) into out_ips[] (packed BE)
87// + out_ports[]. Returns peer count, or -1 if no compact peers field.
88func nx_tracker_parse_peers(resp: *u8, n: i64, out_ips: *i64, out_ports: *i64, max: i64) -> i64 {
89 let pk: i64 = nx_bc_dict_get(resp, 0, n, "peers", 5)
90 if pk < 0 { return 0 - 1 }
91 if nx_bc_type(resp, pk, n) != NX_BC_STR { return 0 - 1 } // dict-model peers not handled here
92 let so: *i64 = sys_mmap(8) as *i64
93 let sl: *i64 = sys_mmap(8) as *i64
94 nx_bc_str(resp, pk, n, so, sl)
95 let total: i64 = sl[0] / 6
96 var c: i64 = 0
97 var i: i64 = 0
98 while i < total {
99 if c < max {
100 let b: i64 = so[0] + i * 6
101 out_ips[c] = ((resp[b] as i64) << 24) | ((resp[b + 1] as i64) << 16) | ((resp[b + 2] as i64) << 8) | (resp[b + 3] as i64)
102 out_ports[c] = ((resp[b + 4] as i64) << 8) | (resp[b + 5] as i64)
103 c = c + 1
104 }
105 i = i + 1
106 }
107 return c
108}