Initial commit: Naut-Torrent — from-scratch 10 GbE BitTorrent client

A maintainable, extensible BitTorrent client (C11, Linux/io_uring) targeting
10 GbE saturation. All torrent functionality is built from scratch; liburing
is the only linked third-party dependency on the data path.

Implements Phases 1-7 of the roadmap:
- core: page-aligned buffer pool, MPMC/Treiber queues, bitfields, worker pool
- crypto: SHA-1/256 (SHA-NI + scalar), Merkle (BEP-52), RC4 (MSE)
- bencode/metainfo: zero-copy parser, v1/v2/hybrid .torrent + magnet
- peer: sans-IO wire codec, MSE/PE handshake state machine, BEP-10, ut_metadata, PEX
- piece/storage: block-level multi-peer engine, rarest-first + endgame,
  per-file completion events + single-file relocate (move-as-you-finish)
- tracker/dht: HTTP + UDP (BEP-15) trackers, BEP-5 KRPC iterative lookup
- platform: io_uring reactor (SQPOLL, registered buffers, SEND_ZC)
- surface: versioned RPC, native plugin ABI, sandboxed Lua scripting, nautd/nautctl

Verified against libtorrent (single/multi/hybrid, MSE, magnet-via-DHT, swarm);
unit + interop tests green; ASan/UBSan/TSan clean. Scripting reference in
docs/scripting.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ookami125 2026-06-15 12:12:00 -04:00
commit 2178d6a70c
121 changed files with 12644 additions and 0 deletions

310
src/bencode/bencode.c Normal file
View file

@ -0,0 +1,310 @@
#include "naut/bencode.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_DEPTH 100
#define MAX_NODES (4u * 1024 * 1024) /* hard cap on tree size */
/* --- arena: stable-address bump allocator -------------------------------- */
typedef struct arena_chunk {
struct arena_chunk *next;
size_t used, cap;
uint8_t data[];
} arena_chunk;
struct naut_bc_doc {
arena_chunk *chunks;
naut_bc *root;
size_t nodes;
};
static void *arena_alloc(naut_bc_doc *d, size_t n) {
n = (n + 7) & ~(size_t)7;
arena_chunk *c = d->chunks;
if (!c || c->used + n > c->cap) {
size_t cap = n > 65536 ? n : 65536;
c = malloc(sizeof(arena_chunk) + cap);
if (!c) return NULL;
c->next = d->chunks; c->used = 0; c->cap = cap;
d->chunks = c;
}
void *p = c->data + c->used;
c->used += n;
return p;
}
/* --- parser -------------------------------------------------------------- */
typedef struct {
const uint8_t *p, *end;
naut_bc_doc *doc;
naut_err err;
} P;
/* temporary growable vector of naut_bc used while a container's size is unknown */
typedef struct { naut_bc *v; size_t n, cap; } vec;
static bool vec_push(vec *x, naut_bc item) {
if (x->n == x->cap) {
size_t nc = x->cap ? x->cap * 2 : 8;
naut_bc *nv = realloc(x->v, nc * sizeof(naut_bc));
if (!nv) return false;
x->v = nv; x->cap = nc;
}
x->v[x->n++] = item;
return true;
}
static bool parse_value(P *s, naut_bc *out, int depth);
static bool parse_uint(P *s, size_t *out, uint8_t term) {
/* decimal, no leading zeros (except a lone "0"), terminated by `term` */
if (s->p >= s->end) return false;
size_t val = 0;
const uint8_t *start = s->p;
if (*s->p == '0') { /* only "0" then term */
s->p++;
if (s->p >= s->end || *s->p != term) return false;
*out = 0; s->p++;
return true;
}
while (s->p < s->end && *s->p >= '0' && *s->p <= '9') {
if (val > (SIZE_MAX - 9) / 10) return false; /* overflow guard */
val = val * 10 + (size_t)(*s->p - '0');
s->p++;
}
if (s->p == start) return false; /* no digits */
if (s->p >= s->end || *s->p != term) return false;
s->p++;
*out = val;
return true;
}
static bool parse_int(P *s, naut_bc *out) {
s->p++; /* 'i' */
bool neg = false;
if (s->p < s->end && *s->p == '-') { neg = true; s->p++; }
/* digits up to 'e', no leading zero, no "-0" */
if (s->p >= s->end) return false;
const uint8_t *d0 = s->p;
int64_t val = 0;
if (*s->p == '0') {
s->p++;
if (s->p >= s->end || *s->p != 'e') return false; /* "i0e" only */
if (neg) return false; /* "-0" illegal */
} else {
while (s->p < s->end && *s->p >= '0' && *s->p <= '9') {
if (val > (INT64_MAX - 9) / 10) return false;
val = val * 10 + (*s->p - '0');
s->p++;
}
if (s->p == d0) return false;
if (s->p >= s->end || *s->p != 'e') return false;
}
s->p++; /* 'e' */
out->type = NAUT_BC_INT;
out->v.i = neg ? -val : val;
return true;
}
static bool parse_string(P *s, naut_bc *out) {
size_t n;
if (!parse_uint(s, &n, ':')) return false;
if ((size_t)(s->end - s->p) < n) return false;
out->type = NAUT_BC_STR;
out->v.str.p = s->p;
out->v.str.n = n;
s->p += n;
return true;
}
static bool parse_list(P *s, naut_bc *out, int depth) {
s->p++; /* 'l' */
vec items = {0};
while (s->p < s->end && *s->p != 'e') {
naut_bc item;
if (!parse_value(s, &item, depth + 1)) { free(items.v); return false; }
if (!vec_push(&items, item)) { free(items.v); s->err = NAUT_ERR_NOMEM; return false; }
}
if (s->p >= s->end) { free(items.v); return false; } /* missing 'e' */
s->p++;
naut_bc *arr = NULL;
if (items.n) {
arr = arena_alloc(s->doc, items.n * sizeof(naut_bc));
if (!arr) { free(items.v); s->err = NAUT_ERR_NOMEM; return false; }
memcpy(arr, items.v, items.n * sizeof(naut_bc));
}
free(items.v);
out->type = NAUT_BC_LIST;
out->v.list.items = arr;
out->v.list.count = items.n;
return true;
}
static bool parse_dict(P *s, naut_bc *out, int depth) {
s->p++; /* 'd' */
naut_bc_pair *pairs = NULL; size_t n = 0, cap = 0;
while (s->p < s->end && *s->p != 'e') {
naut_bc key;
if (s->p >= s->end || *s->p < '0' || *s->p > '9') goto fail; /* key must be string */
if (!parse_string(s, &key)) goto fail;
naut_bc *val = arena_alloc(s->doc, sizeof(naut_bc));
if (!val) { s->err = NAUT_ERR_NOMEM; goto fail; }
if (!parse_value(s, val, depth + 1)) goto fail;
if (n == cap) {
size_t nc = cap ? cap * 2 : 8;
naut_bc_pair *np = realloc(pairs, nc * sizeof(*np));
if (!np) { s->err = NAUT_ERR_NOMEM; goto fail; }
pairs = np; cap = nc;
}
pairs[n].kp = key.v.str.p; pairs[n].kn = key.v.str.n; pairs[n].val = val;
n++;
}
if (s->p >= s->end) goto fail; /* missing 'e' */
s->p++;
{
naut_bc_pair *arr = NULL;
if (n) {
arr = arena_alloc(s->doc, n * sizeof(naut_bc_pair));
if (!arr) { s->err = NAUT_ERR_NOMEM; goto fail; }
memcpy(arr, pairs, n * sizeof(naut_bc_pair));
}
free(pairs);
out->type = NAUT_BC_DICT;
out->v.dict.pairs = arr;
out->v.dict.count = n;
return true;
}
fail:
free(pairs);
return false;
}
static bool parse_value(P *s, naut_bc *out, int depth) {
if (depth > MAX_DEPTH) { s->err = NAUT_ERR_PROTO; return false; }
if (++s->doc->nodes > MAX_NODES) { s->err = NAUT_ERR_PROTO; return false; }
if (s->p >= s->end) return false;
const uint8_t *raw0 = s->p;
bool ok;
switch (*s->p) {
case 'i': ok = parse_int(s, out); break;
case 'l': ok = parse_list(s, out, depth); break;
case 'd': ok = parse_dict(s, out, depth); break;
default:
if (*s->p >= '0' && *s->p <= '9') ok = parse_string(s, out);
else { s->err = NAUT_ERR_PROTO; return false; }
}
if (ok) { out->raw = raw0; out->raw_len = (size_t)(s->p - raw0); }
return ok;
}
naut_err naut_bc_parse_prefix(const uint8_t *data, size_t len,
naut_bc_doc **out, size_t *consumed) {
if (!data || !out || !consumed) return NAUT_ERR_INVAL;
naut_bc_doc *doc = calloc(1, sizeof(*doc));
if (!doc) return NAUT_ERR_NOMEM;
P s = { .p = data, .end = data + len, .doc = doc, .err = NAUT_ERR_PROTO };
naut_bc *root = arena_alloc(doc, sizeof(naut_bc));
if (!root) { naut_bc_free(doc); return NAUT_ERR_NOMEM; }
if (!parse_value(&s, root, 0)) {
naut_err e = s.err ? s.err : NAUT_ERR_PROTO;
naut_bc_free(doc);
return e;
}
doc->root = root;
*out = doc;
*consumed = (size_t)(s.p - data);
return NAUT_OK;
}
naut_err naut_bc_parse(const uint8_t *data, size_t len, naut_bc_doc **out) {
size_t consumed = 0;
naut_err e = naut_bc_parse_prefix(data, len, out, &consumed);
if (e != NAUT_OK) return e;
if (consumed != len) {
naut_bc_free(*out);
*out = NULL;
return NAUT_ERR_PROTO;
}
return NAUT_OK;
}
const naut_bc *naut_bc_root(const naut_bc_doc *doc) { return doc ? doc->root : NULL; }
void naut_bc_free(naut_bc_doc *doc) {
if (!doc) return;
arena_chunk *c = doc->chunks;
while (c) { arena_chunk *n = c->next; free(c); c = n; }
free(doc);
}
/* --- accessors ----------------------------------------------------------- */
const naut_bc *naut_bc_dict_get(const naut_bc *d, const char *key) {
if (!d || d->type != NAUT_BC_DICT) return NULL;
size_t klen = strlen(key);
for (size_t i = 0; i < d->v.dict.count; i++) {
const naut_bc_pair *p = &d->v.dict.pairs[i];
if (p->kn == klen && memcmp(p->kp, key, klen) == 0) return p->val;
}
return NULL;
}
const naut_bc *naut_bc_list_at(const naut_bc *l, size_t i) {
if (!l || l->type != NAUT_BC_LIST || i >= l->v.list.count) return NULL;
return &l->v.list.items[i];
}
bool naut_bc_get_int(const naut_bc *v, int64_t *out) {
if (!v || v->type != NAUT_BC_INT) { *out = 0; return false; }
*out = v->v.i; return true;
}
bool naut_bc_get_str(const naut_bc *v, const uint8_t **p, size_t *n) {
if (!v || v->type != NAUT_BC_STR) { *p = NULL; *n = 0; return false; }
*p = v->v.str.p; *n = v->v.str.n; return true;
}
bool naut_bc_str_eq(const naut_bc *v, const char *s) {
if (!v || v->type != NAUT_BC_STR) return false;
size_t n = strlen(s);
return v->v.str.n == n && memcmp(v->v.str.p, s, n) == 0;
}
/* --- encoder ------------------------------------------------------------- */
void naut_bc_w_init(naut_bc_writer *w) { memset(w, 0, sizeof(*w)); }
void naut_bc_w_free(naut_bc_writer *w) { free(w->buf); memset(w, 0, sizeof(*w)); }
static bool w_reserve(naut_bc_writer *w, size_t extra) {
if (w->err) return false;
if (w->len + extra > w->cap) {
size_t nc = w->cap ? w->cap * 2 : 256;
while (nc < w->len + extra) nc *= 2;
uint8_t *nb = realloc(w->buf, nc);
if (!nb) { w->err = NAUT_ERR_NOMEM; return false; }
w->buf = nb; w->cap = nc;
}
return true;
}
static void w_putc(naut_bc_writer *w, char c) {
if (w_reserve(w, 1)) w->buf[w->len++] = (uint8_t)c;
}
static void w_raw(naut_bc_writer *w, const void *p, size_t n) {
if (w_reserve(w, n)) { memcpy(w->buf + w->len, p, n); w->len += n; }
}
static void w_decimal(naut_bc_writer *w, int64_t v) {
char tmp[24];
int n = snprintf(tmp, sizeof tmp, "%lld", (long long)v);
w_raw(w, tmp, (size_t)n);
}
void naut_bc_w_int(naut_bc_writer *w, int64_t v) {
w_putc(w, 'i'); w_decimal(w, v); w_putc(w, 'e');
}
void naut_bc_w_bytes(naut_bc_writer *w, const void *p, size_t n) {
w_decimal(w, (int64_t)n); w_putc(w, ':'); w_raw(w, p, n);
}
void naut_bc_w_cstr(naut_bc_writer *w, const char *s) { naut_bc_w_bytes(w, s, strlen(s)); }
void naut_bc_w_list_begin(naut_bc_writer *w) { w_putc(w, 'l'); }
void naut_bc_w_dict_begin(naut_bc_writer *w) { w_putc(w, 'd'); }
void naut_bc_w_end(naut_bc_writer *w) { w_putc(w, 'e'); }