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:
commit
2178d6a70c
121 changed files with 12644 additions and 0 deletions
310
src/bencode/bencode.c
Normal file
310
src/bencode/bencode.c
Normal 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'); }
|
||||
Loading…
Add table
Add a link
Reference in a new issue