-
Notifications
You must be signed in to change notification settings - Fork 0
Implementing a Host
github-actions[bot] edited this page Aug 27, 2026
·
1 revision
typebit::Host is the only interface you implement. It has ~18 methods, all
no_std-friendly. The engine calls them from tick() on your thread, so
they must not block for long (use non-blocking sockets).
pub trait Host {
fn now_ms(&self) -> u64; // clock (ms)
fn fill_random(&mut self, buf: &mut [u8]); // CSPRNG
fn log(&mut self, level: LogLevel, msg: &str);
// HTTP(S) GET → append body to `out` (trackers, web seeds, IPFS gateways)
fn http_get(&mut self, url: &str, timeout_ms: u64, out: &mut Vec<u8>)
-> Result<()>;
// DNS: resolve a hostname for the DHT bootstrap routers (BEP-5).
// Optional — returning None disables DHT bootstrap (HTTP/UDP trackers
// still work). The FFI `HostCbs.resolve_host` is the C twin of this.
fn resolve_host(&self, host: &str, port: u16) -> Option<NetAddr>;
// TCP (non-blocking)
fn tcp_connect(&mut self, addr: &NetAddr) -> Result<ConnId>;
fn tcp_connect_done(&mut self, id: ConnId) -> Result<()>; // Ok=established
fn tcp_send(&mut self, id: ConnId, data: &[u8]) -> Result<usize>;
fn tcp_recv(&mut self, id: ConnId, buf: &mut [u8]) -> Result<usize>;
fn tcp_close(&mut self, id: ConnId);
// UDP (DHT, UDP trackers)
fn udp_open(&mut self, port: u16) -> Result<()>;
fn udp_send(&mut self, addr: &NetAddr, data: &[u8]) -> Result<()>;
fn udp_recv(&mut self, buf: &mut [u8]) -> Result<(NetAddr, usize)>;
// Files (piece data)
fn disk_open(&mut self, path: &str) -> Result<DiskId>;
fn disk_read(&mut self, id: DiskId, offset: u64, buf: &mut [u8]) -> Result<usize>;
fn disk_write(&mut self, id: DiskId, offset: u64, data: &[u8]) -> Result<()>;
fn disk_prealloc(&mut self, id: DiskId, size: u64) -> Result<()>;
fn disk_flush(&mut self, id: DiskId) -> Result<()>;
fn disk_close(&mut self, id: DiskId);
}-
tcp_recv/udp_recvreturnErr(Error::WouldBlock)when there is no data right now. Do not block in these. -
tcp_connectreturns a handle immediately (non-blocking connect); the engine polls withtcp_connect_done. -
http_getmay block up totimeout_ms; the engine uses it for trackers and web seeds. - Disk offsets are absolute within one file (per-file handles).
-
UDP is optional, never fatal. The engine opens the UDP socket lazily
and only when something needs it (DHT enabled, a UDP tracker, or port
mapping). If
udp_openfails,start()still succeeds: DHT and UDP trackers are disabled, anEngineEvent::Error { code: 0 }is emitted, and HTTP trackers + peer transport keep working. To actually get DHT working, implementresolve_hostand realudp_open/udp_send/udp_recv.
use std::net::TcpStream;
use typebit::platform::{ConnId, DiskId, Host, LogLevel, NetAddr};
use typebit::{Error, Result};
pub struct StdHost {
tcp: HashMap<ConnId, TcpStream>,
disks: HashMap<DiskId, std::fs::File>,
next_id: u32,
}
impl Host for StdHost {
fn now_ms(&self) -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
fn fill_random(&mut self, buf: &mut [u8]) {
// os random (e.g. getrandom or /dev/urandom)
}
fn tcp_connect(&mut self, addr: &NetAddr) -> Result<ConnId> {
let (ip, port) = match addr {
NetAddr::V4(ip, p) => (std::net::Ipv4Addr::from(*ip), *p),
NetAddr::V6(ip, p) => (std::net::Ipv6Addr::from(*ip), *p),
};
let stream = TcpStream::connect((ip, port)).map_err(|_| Error::Io)?;
stream.set_nonblocking(true).map_err(|_| Error::Io)?;
let id = self.next_id;
self.next_id += 1;
self.tcp.insert(id, stream);
Ok(id)
}
fn tcp_recv(&mut self, id: ConnId, buf: &mut [u8]) -> Result<usize> {
use std::io::Read;
match self.tcp.get_mut(&id).unwrap().read(buf) {
Ok(n) => Ok(n),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Err(Error::WouldBlock),
Err(_) => Err(Error::Io),
}
}
// ... and the rest. See examples/minimal_host.rs for a complete mock.
}| Method | Default | Needed for |
|---|---|---|
resolve_host(host, port) -> Option<NetAddr> |
None |
DHT bootstrap |
resolve_host_all(host, port) -> Vec<NetAddr> |
resolve_host only |
UDP tracker multi-address fallback |
resolve_host_async + take_resolved_hosts()
|
false / empty |
non-blocking DHT bootstrap DNS |
http_post(url, body, timeout, out) |
Err(NotSupported) |
UPnP IGD SOAP |
http_get_range(url, start, end, timeout, out) |
Err(NotSupported) |
web seeds (BEP-19) |
http_get_async(url, timeout) -> u64 + http_take_done()
|
0 / empty |
non-blocking tracker / web seed |
udp_multicast_send / udp_join_multicast
|
udp_send / no-op |
LSD (BEP-14), SSDP |
default_gateway() -> Option<NetAddr> |
None |
NAT-PMP |
local_ip() -> Option<NetAddr> |
None |
UPnP IGD NewInternalClient
|
tcp_recv_buf_size() -> usize |
64 KiB | receive-buffer sizing hint |
Note: keep the
outbuffers of every HTTP callback bounded. The engine enforces caps (MAX_HTTP_BODY/ request window), but a host implementation should also defend against hostile response bodies.
-
Engine::new(host, config)— one per app -
add_torrent(&bytes, save_dir)oradd_magnet(uri, save_dir)→ infohash start(&hash)- on a timer:
tick(), thentake_events()and forward events to your UI - inbound sockets:
on_inbound_connection(conn, addr) - persistence:
save_state()/load_state(&state, now)
That's the whole integration surface. Everything else is internal.