typebit 0.1.9

A no_std BitTorrent v1/v2 engine core (DHT, PEX, LSD, uTP, web seeds, trackers, SOCKS5) with provable-download receipts and a semantic utility scheduler; unified link parser for eD2k/Xunlei/IPFS/Kad/netdisk (parsing only).
Documentation
# Implementing a Host


`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).

## The trait


```rust
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);
}
```

## Conventions the engine relies on


- `tcp_recv` / `udp_recv` return `Err(Error::WouldBlock)` when there is no
  data right now. Do **not** block in these.
- `tcp_connect` returns a handle immediately (non-blocking connect); the
  engine polls with `tcp_connect_done`.
- `http_get` may block up to `timeout_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_open` fails, `start()` still succeeds: DHT and UDP
  trackers are disabled, an `EngineEvent::Error { code: 0 }` is emitted,
  and HTTP trackers + peer transport keep working. To actually get DHT
  working, implement `resolve_host` and real `udp_open`/`udp_send`/
  `udp_recv`.

## A real (std) host in ~60 lines

```rust
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.
}
```

## Optional capabilities (all have defaults)


| 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 `out` buffers 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.

## Integration checklist


1. `Engine::new(host, config)` — one per app
2. `add_torrent(&bytes, save_dir)` or `add_magnet(uri, save_dir)` → infohash
3. `start(&hash)`
4. on a timer: `tick()`, then `take_events()` and forward events to your UI
5. inbound sockets: `on_inbound_connection(conn, addr)`
6. persistence: `save_state()` / `load_state(&state, now)`

That's the whole integration surface. Everything else is internal.