1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//! The platform abstraction seam: the engine is written against [`Host`]; a
//! concrete implementation (std, Android/Kotlin, iOS/Swift, embedded) only
//! provides ~15 primitives. Transports are **non-blocking** ([`Host::tcp_recv`]
//! returns [`Error::WouldBlock`], `tcp_send` may take a partial prefix), so
//! one engine drives hundreds of peers from a single thread.
use crate::error::{Error, Result};
/// Opaque handle to an open TCP connection.
pub type ConnId = u32;
/// Opaque handle to an open file/backing store.
pub type DiskId = u32;
/// Log severity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum LogLevel {
/// Trace noise.
Trace,
/// Debug.
Debug,
/// Informational.
Info,
/// Warning.
Warn,
/// Error.
Error,
}
/// A network endpoint. Compact, `Copy`, comparable — suitable as map keys.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NetAddr {
/// IPv4 address + port.
V4([u8; 4], u16),
/// IPv6 address + port.
V6([u8; 16], u16),
}
impl NetAddr {
/// IPv4 from parts.
pub const fn v4(a: u8, b: u8, c: u8, d: u8, port: u16) -> Self {
NetAddr::V4([a, b, c, d], port)
}
/// Build from a compact 6-byte `IP:port` (IPv4) blob, as used by trackers,
/// PEX and DHT peer lists.
pub fn from_compact6(b: &[u8]) -> Option<Self> {
if b.len() != 6 {
return None;
}
let mut ip = [0u8; 4];
ip.copy_from_slice(&b[..4]);
Some(NetAddr::V4(ip, u16::from_be_bytes([b[4], b[5]])))
}
/// Build from a compact 18-byte `IP:port` (IPv6) blob.
pub fn from_compact18(b: &[u8]) -> Option<Self> {
if b.len() != 18 {
return None;
}
let mut ip = [0u8; 16];
ip.copy_from_slice(&b[..16]);
Some(NetAddr::V6(ip, u16::from_be_bytes([b[16], b[17]])))
}
/// Compact IPv4 `IP:port` (6 bytes) if this is an IPv4 address.
pub fn to_compact6(&self) -> Option<[u8; 6]> {
match *self {
NetAddr::V4(ip, port) => {
let mut out = [0u8; 6];
out[..4].copy_from_slice(&ip);
out[4..].copy_from_slice(&port.to_be_bytes());
Some(out)
}
NetAddr::V6(_, _) => None,
}
}
/// Compact IPv6 `IP:port` (18 bytes) if this is an IPv6 address.
pub fn to_compact18(&self) -> Option<[u8; 18]> {
match *self {
NetAddr::V6(ip, port) => {
let mut out = [0u8; 18];
out[..16].copy_from_slice(&ip);
out[16..].copy_from_slice(&port.to_be_bytes());
Some(out)
}
NetAddr::V4(_, _) => None,
}
}
/// The port, regardless of family.
pub fn port(&self) -> u16 {
match *self {
NetAddr::V4(_, p) | NetAddr::V6(_, p) => p,
}
}
/// Human-readable form into a caller buffer (allocator-free).
pub fn write_to(&self, buf: &mut [u8]) -> Result<usize> {
let mut w = 0usize;
match *self {
NetAddr::V4(ip, port) => {
for (i, o) in ip.iter().enumerate() {
if i > 0 {
buf[w] = b'.';
w += 1;
}
let n = write_u32(buf.get_mut(w..).ok_or(Error::TooLarge)?, *o as u32);
w += n;
}
let n = write_u16(buf.get_mut(w..).ok_or(Error::TooLarge)?, port);
w += n;
}
NetAddr::V6(ip, port) => {
for (i, g) in ip.chunks(2).enumerate() {
if i > 0 {
buf[w] = b':';
w += 1;
}
let v = u16::from_be_bytes([g[0], g[1]]);
let n = write_u16_hex(buf.get_mut(w..).ok_or(Error::TooLarge)?, v);
w += n;
}
let n = write_u16(buf.get_mut(w..).ok_or(Error::TooLarge)?, port);
w += n;
}
}
Ok(w)
}
/// Allocate a `String` representation (requires `alloc`).
pub fn to_alloc_string(&self) -> alloc::string::String {
let mut buf = [0u8; 64];
let n = self.write_to(&mut buf).unwrap_or(0);
alloc::string::String::from_utf8_lossy(&buf[..n]).into_owned()
}
}
impl core::fmt::Display for NetAddr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut buf = [0u8; 64];
let n = self.write_to(&mut buf).unwrap_or(0);
f.write_str(core::str::from_utf8(&buf[..n]).unwrap_or("<addr>"))
}
}
fn write_u32(buf: &mut [u8], mut v: u32) -> usize {
let mut n = 0;
if v == 0 {
buf[0] = b'0';
return 1;
}
let mut tmp = [0u8; 10];
let mut i = 0;
while v > 0 {
tmp[i] = b'0' + (v % 10) as u8;
v /= 10;
i += 1;
}
while i > 0 {
i -= 1;
buf[n] = tmp[i];
n += 1;
}
n
}
fn write_u16(buf: &mut [u8], v: u16) -> usize {
let mut n = 0;
if v == 0 {
buf[0] = b'0';
return 1;
}
let mut tmp = [0u8; 5];
let mut vv = v;
let mut i = 0;
while vv > 0 {
tmp[i] = b'0' + (vv % 10) as u8;
vv /= 10;
i += 1;
}
while i > 0 {
i -= 1;
buf[n] = tmp[i];
n += 1;
}
n
}
fn write_u16_hex(buf: &mut [u8], v: u16) -> usize {
const HEX: &[u8; 16] = b"0123456789abcdef";
buf[0] = HEX[((v >> 12) & 0xf) as usize];
buf[1] = HEX[((v >> 8) & 0xf) as usize];
buf[2] = HEX[((v >> 4) & 0xf) as usize];
buf[3] = HEX[(v & 0xf) as usize];
4
}
/// The platform seam. Implementations must be cheap and must not block
/// longer than necessary.
pub trait Host {
/// Monotonic-ish milliseconds clock. Used for deadlines, retry back-off
/// and rate windows. Does not need wall-clock precision.
fn now_ms(&self) -> u64;
/// Fill `buf` with cryptographically secure random bytes.
fn fill_random(&mut self, buf: &mut [u8]);
/// Log a message.
fn log(&mut self, level: LogLevel, msg: &str);
// ---------- HTTP (tracker announces, web seeds, webseeds) ----------
/// Perform a blocking HTTP GET and append the response body to `out`.
/// `timeout_ms` bounds the whole operation. Used by tracker and web-seed
/// transports in the host. Returns `Err(Timeout)` on deadline.
fn http_get(&mut self, url: &str, timeout_ms: u64, out: &mut alloc::vec::Vec<u8>)
-> Result<()>;
// ---------- TCP peers ----------
/// Begin a non-blocking connect to `addr`. Returns a handle immediately.
fn tcp_connect(&mut self, addr: &NetAddr) -> Result<ConnId>;
/// Poll a pending connect. `Ok(())` = established, `Err(WouldBlock)` =
/// still in progress, any other error = connection failed.
fn tcp_connect_done(&mut self, id: ConnId) -> Result<()>;
/// Send up to `data.len()` bytes (non-blocking). Returns bytes accepted.
fn tcp_send(&mut self, id: ConnId, data: &[u8]) -> Result<usize>;
/// Receive into `buf` (non-blocking). `Err(WouldBlock)` = no data.
fn tcp_recv(&mut self, id: ConnId, buf: &mut [u8]) -> Result<usize>;
/// Close a connection and free the handle.
fn tcp_close(&mut self, id: ConnId);
/// Socket receive buffer size hint (bytes) so the engine can size its
/// read buffers.
fn tcp_recv_buf_size(&self) -> usize {
64 * 1024
}
// ---------- UDP (DHT, UDP trackers) ----------
/// Open the UDP socket for outgoing+incoming datagrams (DHT / UDP tracker).
fn udp_open(&mut self, port: u16) -> Result<()>;
/// Send one datagram.
fn udp_send(&mut self, addr: &NetAddr, data: &[u8]) -> Result<()>;
/// Receive one datagram (non-blocking). `Err(WouldBlock)` = none pending.
fn udp_recv(&mut self, buf: &mut [u8]) -> Result<(NetAddr, usize)>;
// ---------- Disk ----------
/// Open (create if missing) a file by path. Returns a handle.
fn disk_open(&mut self, path: &str) -> Result<DiskId>;
/// Read up to `buf.len()` bytes at `offset`.
fn disk_read(&mut self, id: DiskId, offset: u64, buf: &mut [u8]) -> Result<usize>;
/// Write `data` at `offset`.
fn disk_write(&mut self, id: DiskId, offset: u64, data: &[u8]) -> Result<()>;
/// Pre-allocate `size` bytes (sparse where supported) — avoids
/// fragmentation and reduces per-piece allocation churn.
fn disk_prealloc(&mut self, id: DiskId, size: u64) -> Result<()>;
/// Flush buffered writes for this file to stable storage.
fn disk_flush(&mut self, id: DiskId) -> Result<()>;
/// Close a file.
fn disk_close(&mut self, id: DiskId);
}