nx_bencode.nx source
↩ module page · 134 lines · 5353 B
1// nx_bencode.nx -- bencoding parser (bits-up), the BitTorrent keystone.
2//
3// module: nishi-core.torrent.bencode
4// depends: nx_syscalls.nx
5// capability: CORE_COMPUTE
6// wired_status: FULLY_WIRED
7//
8// genealogy_id: bittorrent_bep0003_bencoding
9//
10// The format ALL of BitTorrent is built on (.torrent metainfo, tracker
11// responses, the peer wire protocol's extended messages): integers (i<d>e),
12// byte strings (<len>:<bytes>), lists (l..e), dicts (d<key><val>..e). Parsing
13// is length-driven (strings carry their length), so a real parser is required
14// (you cannot byte-count 'e's -- strings contain arbitrary bytes). First piece
15// of the sovereign torrent client; the tracker-announce step reuses
16// nx_http_get_ua + nx_net_governor (the primitives extracted this session).
17// Iterative skip (no recursion); offsets returned, no copying. Deterministic.
18
19import "nx_syscalls.nx"
20
21const NX_BC_ERR: i64 = 0
22const NX_BC_INT: i64 = 1
23const NX_BC_STR: i64 = 2
24const NX_BC_LIST: i64 = 3
25const NX_BC_DICT: i64 = 4
26
27func _bc_is_digit(c: i64) -> i64 { if c >= 0x30 { if c <= 0x39 { return 1 } } return 0 }
28
29func _bc_bytes_eq(buf: *u8, off: i64, key: *u8, keylen: i64) -> i64 {
30 var i: i64 = 0
31 while i < keylen { if (buf[off + i] as i64) != (key[i] as i64) { return 0 } i = i + 1 }
32 return 1
33}
34
35// type of the bencoded value at off.
36func nx_bc_type(buf: *u8, off: i64, n: i64) -> i64 {
37 if off >= n { return NX_BC_ERR }
38 let c: i64 = buf[off] as i64
39 if c == 0x69 { return NX_BC_INT } // 'i'
40 if c == 0x6C { return NX_BC_LIST } // 'l'
41 if c == 0x64 { return NX_BC_DICT } // 'd'
42 if _bc_is_digit(c) == 1 { return NX_BC_STR }
43 return NX_BC_ERR
44}
45
46// parse i<digits>e at off; writes value to out_val[0]; returns next offset or -1.
47func nx_bc_int(buf: *u8, off: i64, n: i64, out_val: *i64) -> i64 {
48 if off >= n { return 0 - 1 }
49 if (buf[off] as i64) != 0x69 { return 0 - 1 }
50 var p: i64 = off + 1
51 var neg: i64 = 0
52 if p < n { if (buf[p] as i64) == 0x2D { neg = 1; p = p + 1 } }
53 var v: i64 = 0
54 var dr: i64 = 1
55 while dr == 1 { dr = 0; if p < n { if _bc_is_digit(buf[p] as i64) == 1 { v = v * 10 + ((buf[p] as i64) - 0x30); p = p + 1; dr = 1 } } }
56 if p >= n { return 0 - 1 }
57 if (buf[p] as i64) != 0x65 { return 0 - 1 } // 'e'
58 if neg == 1 { out_val[0] = 0 - v } else { out_val[0] = v }
59 return p + 1
60}
61
62// parse <len>:<bytes> at off; writes byte-offset to out_so[0], length to
63// out_sl[0]; returns next offset or -1.
64func nx_bc_str(buf: *u8, off: i64, n: i64, out_so: *i64, out_sl: *i64) -> i64 {
65 var p: i64 = off
66 var len: i64 = 0
67 var dr: i64 = 1
68 while dr == 1 { dr = 0; if p < n { if _bc_is_digit(buf[p] as i64) == 1 { len = len * 10 + ((buf[p] as i64) - 0x30); p = p + 1; dr = 1 } } }
69 if p >= n { return 0 - 1 }
70 if (buf[p] as i64) != 0x3A { return 0 - 1 } // ':'
71 p = p + 1
72 out_so[0] = p
73 out_sl[0] = len
74 return p + len
75}
76
77// skip the value at off; returns next offset or -1. Iterative (depth counter);
78// strings are skipped by their length so embedded i/l/d/e bytes are safe.
79func nx_bc_skip(buf: *u8, off: i64, n: i64) -> i64 {
80 var p: i64 = off
81 var depth: i64 = 0
82 var run: i64 = 1
83 while run == 1 {
84 run = 0
85 if p < n {
86 let c: i64 = buf[p] as i64
87 if c == 0x69 { // 'i' integer
88 p = p + 1
89 var ir: i64 = 1
90 while ir == 1 { ir = 0; if p < n { if (buf[p] as i64) != 0x65 { p = p + 1; ir = 1 } } }
91 if p < n { p = p + 1 }
92 if depth > 0 { run = 1 }
93 }
94 if c == 0x6C { depth = depth + 1; p = p + 1; run = 1 } // 'l'
95 if c == 0x64 { depth = depth + 1; p = p + 1; run = 1 } // 'd'
96 if c == 0x65 { depth = depth - 1; p = p + 1; if depth > 0 { run = 1 } } // 'e'
97 if _bc_is_digit(c) == 1 { // string
98 var len: i64 = 0
99 var dr: i64 = 1
100 while dr == 1 { dr = 0; if p < n { if _bc_is_digit(buf[p] as i64) == 1 { len = len * 10 + ((buf[p] as i64) - 0x30); p = p + 1; dr = 1 } } }
101 if p < n { if (buf[p] as i64) == 0x3A { p = p + 1 + len } }
102 if depth > 0 { run = 1 }
103 }
104 }
105 }
106 return p
107}
108
109// in the dict at dict_off, find `key`; returns the VALUE's offset, or -1.
110func nx_bc_dict_get(buf: *u8, dict_off: i64, n: i64, key: *u8, keylen: i64) -> i64 {
111 if dict_off >= n { return 0 - 1 }
112 if (buf[dict_off] as i64) != 0x64 { return 0 - 1 } // 'd'
113 var p: i64 = dict_off + 1
114 var run: i64 = 1
115 while run == 1 {
116 run = 0
117 if p < n {
118 if (buf[p] as i64) != 0x65 { // not 'e'
119 let sob: *i64 = sys_mmap(8) as *i64
120 let slb: *i64 = sys_mmap(8) as *i64
121 let p2: i64 = nx_bc_str(buf, p, n, sob, slb)
122 if p2 < 0 { return 0 - 1 }
123 var hit: i64 = 0
124 if slb[0] == keylen { if _bc_bytes_eq(buf, sob[0], key, keylen) == 1 { hit = 1 } }
125 if hit == 1 { return p2 }
126 let p3: i64 = nx_bc_skip(buf, p2, n)
127 if p3 < 0 { return 0 - 1 }
128 p = p3
129 run = 1
130 }
131 }
132 }
133 return 0 - 1
134}