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'); }

98
src/core/bitfield.c Normal file
View file

@ -0,0 +1,98 @@
#include "naut/bitfield.h"
#include <stdlib.h>
#include <string.h>
naut_err naut_bitfield_init(naut_bitfield *bf, size_t nbits) {
size_t nwords = (nbits + 63) / 64;
bf->words = nwords ? calloc(nwords, sizeof(uint64_t)) : NULL;
if (nwords && !bf->words) return NAUT_ERR_NOMEM;
bf->nbits = nbits;
bf->nwords = nwords;
return NAUT_OK;
}
void naut_bitfield_free(naut_bitfield *bf) {
free(bf->words);
bf->words = NULL;
bf->nbits = bf->nwords = 0;
}
/* Clear the unused high bits of the last word so count/all_set stay correct. */
static void mask_tail(naut_bitfield *bf) {
size_t rem = bf->nbits & 63;
if (rem && bf->nwords)
bf->words[bf->nwords - 1] &= (((uint64_t)1 << rem) - 1);
}
void naut_bitfield_set_all(naut_bitfield *bf) {
memset(bf->words, 0xff, bf->nwords * sizeof(uint64_t));
mask_tail(bf);
}
void naut_bitfield_clear_all(naut_bitfield *bf) {
memset(bf->words, 0, bf->nwords * sizeof(uint64_t));
}
size_t naut_bitfield_count(const naut_bitfield *bf) {
size_t n = 0;
for (size_t i = 0; i < bf->nwords; i++)
n += (size_t)__builtin_popcountll(bf->words[i]);
return n;
}
bool naut_bitfield_all_set(const naut_bitfield *bf) {
if (bf->nwords == 0) return true;
for (size_t i = 0; i + 1 < bf->nwords; i++)
if (bf->words[i] != ~(uint64_t)0) return false;
size_t rem = bf->nbits & 63;
uint64_t last = bf->words[bf->nwords - 1];
uint64_t want = rem ? (((uint64_t)1 << rem) - 1) : ~(uint64_t)0;
return last == want;
}
size_t naut_bitfield_find_set(const naut_bitfield *bf, size_t from) {
if (from >= bf->nbits) return SIZE_MAX;
size_t w = from >> 6;
uint64_t word = bf->words[w] & (~(uint64_t)0 << (from & 63));
for (;;) {
if (word) {
size_t bit = (w << 6) + (size_t)__builtin_ctzll(word);
return bit < bf->nbits ? bit : SIZE_MAX;
}
if (++w >= bf->nwords) return SIZE_MAX;
word = bf->words[w];
}
}
size_t naut_bitfield_find_zero(const naut_bitfield *bf, size_t from) {
if (from >= bf->nbits) return SIZE_MAX;
size_t w = from >> 6;
uint64_t word = ~bf->words[w] & (~(uint64_t)0 << (from & 63));
for (;;) {
if (word) {
size_t bit = (w << 6) + (size_t)__builtin_ctzll(word);
return bit < bf->nbits ? bit : SIZE_MAX;
}
if (++w >= bf->nwords) return SIZE_MAX;
word = ~bf->words[w];
}
}
void naut_bitfield_from_wire(naut_bitfield *bf, const uint8_t *bytes, size_t nbytes) {
naut_bitfield_clear_all(bf);
size_t bits = NAUT_MIN(bf->nbits, nbytes * 8);
for (size_t i = 0; i < bits; i++) {
/* BEP-3: bit 0 is the MSB of byte 0 */
if ((bytes[i >> 3] >> (7 - (i & 7))) & 1u)
naut_bitfield_set(bf, i);
}
}
void naut_bitfield_to_wire(const naut_bitfield *bf, uint8_t *bytes, size_t nbytes) {
memset(bytes, 0, nbytes);
size_t bits = NAUT_MIN(bf->nbits, nbytes * 8);
for (size_t i = 0; i < bits; i++) {
if (naut_bitfield_test(bf, i))
bytes[i >> 3] |= (uint8_t)(1u << (7 - (i & 7)));
}
}

154
src/core/buf.c Normal file
View file

@ -0,0 +1,154 @@
#include "naut/buf.h"
#include "naut/log.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <linux/mempolicy.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
struct naut_bufpool {
_Atomic(naut_buf *) free_head; /* Treiber stack: MP push, SC pop */
_Atomic uint32_t avail;
naut_buf *meta; /* block_count headers */
uint8_t *slab; /* mmap'd, page-aligned data */
size_t slab_bytes;
uint32_t block_size;
uint32_t block_count;
bool hugepages;
};
static void *map_slab(size_t bytes, bool huge, int numa_node) {
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
void *p = MAP_FAILED;
if (huge) {
p = mmap(NULL, bytes, PROT_READ | PROT_WRITE,
flags | MAP_HUGETLB, -1, 0);
if (p == MAP_FAILED)
NAUT_WARN("hugepage slab (%zu bytes) failed, using 4K pages", bytes);
}
if (p == MAP_FAILED)
p = mmap(NULL, bytes, PROT_READ | PROT_WRITE, flags, -1, 0);
if (p != MAP_FAILED) {
#ifdef SYS_mbind
if (numa_node >= 0 &&
numa_node < (int)(sizeof(unsigned long) * 8)) {
unsigned long mask = 1ul << (unsigned)numa_node;
long rc = syscall(SYS_mbind, p, bytes, MPOL_BIND, &mask,
sizeof(mask) * 8, 0);
if (rc != 0)
NAUT_WARN("NUMA bind node %d failed: %s",
numa_node, strerror(errno));
}
#else
(void)numa_node;
#endif
#ifdef MADV_HUGEPAGE
if (!huge) (void)madvise(p, bytes, MADV_HUGEPAGE);
#endif
#ifdef MADV_DONTDUMP
(void)madvise(p, bytes, MADV_DONTDUMP);
#endif
}
return p == MAP_FAILED ? NULL : p;
}
naut_bufpool *naut_bufpool_create(uint32_t block_size, uint32_t block_count,
bool use_hugepages) {
return naut_bufpool_create_on_node(block_size, block_count,
use_hugepages, -1);
}
naut_bufpool *naut_bufpool_create_on_node(uint32_t block_size,
uint32_t block_count,
bool use_hugepages,
int numa_node) {
if (block_size == 0 || (block_size & (NAUT_PAGE - 1)) != 0 || block_count == 0) {
NAUT_ERROR("bufpool: block_size must be a nonzero multiple of %u", NAUT_PAGE);
return NULL;
}
naut_bufpool *p = calloc(1, sizeof(*p));
if (!p) return NULL;
p->block_size = block_size;
p->block_count = block_count;
p->hugepages = use_hugepages;
p->slab_bytes = (size_t)block_size * block_count;
p->meta = calloc(block_count, sizeof(naut_buf));
p->slab = map_slab(p->slab_bytes, use_hugepages, numa_node);
if (!p->meta || !p->slab) { naut_bufpool_destroy(p); return NULL; }
/* Build the freelist. Index 0 ends at the bottom of the stack. */
atomic_store_explicit(&p->free_head, NULL, memory_order_relaxed);
for (uint32_t i = 0; i < block_count; i++) {
naut_buf *b = &p->meta[i];
b->cap = block_size;
b->idx = i;
b->pool = p;
b->data = p->slab + (size_t)i * block_size;
atomic_store_explicit(&b->refcnt, 0, memory_order_relaxed);
b->fnext = atomic_load_explicit(&p->free_head, memory_order_relaxed);
atomic_store_explicit(&p->free_head, b, memory_order_relaxed);
}
atomic_store_explicit(&p->avail, block_count, memory_order_relaxed);
NAUT_INFO("bufpool: %u blocks x %u bytes (%zu MiB%s%s)",
block_count, block_size, p->slab_bytes >> 20,
use_hugepages ? ", hugepages" : "",
numa_node >= 0 ? ", NUMA-bound" : "");
return p;
}
void naut_bufpool_destroy(naut_bufpool *p) {
if (!p) return;
if (p->slab) munmap(p->slab, p->slab_bytes);
free(p->meta);
free(p);
}
naut_buf *naut_buf_get(naut_bufpool *p) {
/* Single-consumer pop: only the owning reactor calls this, so reading
* head->fnext is safe no other thread can pop `head` from under us. */
naut_buf *head = atomic_load_explicit(&p->free_head, memory_order_acquire);
for (;;) {
if (NAUT_UNLIKELY(!head)) return NULL; /* exhausted */
naut_buf *next = head->fnext;
if (atomic_compare_exchange_weak_explicit(
&p->free_head, &head, next,
memory_order_acquire, memory_order_acquire))
break;
}
atomic_fetch_sub_explicit(&p->avail, 1, memory_order_relaxed);
head->len = 0;
head->fnext = NULL;
atomic_store_explicit(&head->refcnt, 1, memory_order_relaxed);
return head;
}
void naut_buf_put(naut_buf *b) {
if (!b) return;
/* release so a consumer that later pops sees our writes to data[] */
if (atomic_fetch_sub_explicit(&b->refcnt, 1, memory_order_release) != 1)
return; /* still referenced */
atomic_thread_fence(memory_order_acquire);
naut_bufpool *p = b->pool;
naut_buf *head = atomic_load_explicit(&p->free_head, memory_order_relaxed);
do {
b->fnext = head; /* MP push */
} while (!atomic_compare_exchange_weak_explicit(
&p->free_head, &head, b,
memory_order_release, memory_order_relaxed));
atomic_fetch_add_explicit(&p->avail, 1, memory_order_relaxed);
}
uint32_t naut_bufpool_capacity(const naut_bufpool *p) { return p->block_count; }
uint32_t naut_bufpool_available(const naut_bufpool *p) {
return atomic_load_explicit(&p->avail, memory_order_relaxed);
}
void *naut_bufpool_slab(const naut_bufpool *p, size_t *out_bytes) {
if (out_bytes) *out_bytes = p->slab_bytes;
return p->slab;
}

18
src/core/common.c Normal file
View file

@ -0,0 +1,18 @@
#include "naut/common.h"
const char *naut_strerror(naut_err e) {
switch (e) {
case NAUT_OK: return "ok";
case NAUT_ERR_NOMEM: return "out of memory";
case NAUT_ERR_INVAL: return "invalid argument";
case NAUT_ERR_IO: return "i/o error";
case NAUT_ERR_AGAIN: return "would block";
case NAUT_ERR_PROTO: return "protocol error";
case NAUT_ERR_RANGE: return "out of range";
case NAUT_ERR_NOSYS: return "unsupported";
case NAUT_ERR_FULL: return "full";
case NAUT_ERR_EMPTY: return "empty";
case NAUT_ERR_NOTFOUND: return "not found";
default: return "unknown error";
}
}

83
src/core/log.c Normal file
View file

@ -0,0 +1,83 @@
#include "naut/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/uio.h>
#if defined(__linux__)
#include <sys/syscall.h>
static inline long naut__tid(void) { return syscall(SYS_gettid); }
#else
static inline long naut__tid(void) { return 0; }
#endif
static _Atomic naut_log_level g_level = NAUT_LOG_INFO;
void naut_log_set_level(naut_log_level lvl) {
atomic_store_explicit(&g_level, lvl, memory_order_relaxed);
}
naut_log_level naut_log_get_level(void) {
return atomic_load_explicit(&g_level, memory_order_relaxed);
}
static const char *level_str(naut_log_level lvl) {
switch (lvl) {
case NAUT_LOG_ERROR: return "ERROR";
case NAUT_LOG_WARN: return "WARN ";
case NAUT_LOG_INFO: return "INFO ";
case NAUT_LOG_DEBUG: return "DEBUG";
case NAUT_LOG_TRACE: return "TRACE";
default: return "?????";
}
}
/* Format one record into a stack buffer and emit with a single write() so
* concurrent loggers never interleave a line. */
static void emit_v(naut_log_level lvl, const char *file, int line,
const char *fmt, va_list ap) {
char hdr[96];
char msg[1024];
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm;
localtime_r(&ts.tv_sec, &tm);
const char *base = strrchr(file, '/');
base = base ? base + 1 : file;
int hn = snprintf(hdr, sizeof(hdr),
"%02d:%02d:%02d.%03ld [%s] %-5ld %s:%d: ",
tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec / 1000000,
level_str(lvl), naut__tid(), base, line);
int mn = vsnprintf(msg, sizeof(msg), fmt, ap);
if (hn < 0) hn = 0;
if (mn < 0) mn = 0;
if (mn >= (int)sizeof(msg)) mn = sizeof(msg) - 1;
struct iovec iov[3] = {
{ hdr, (size_t)hn },
{ msg, (size_t)mn },
{ (void *)"\n", 1 },
};
ssize_t w = writev(STDERR_FILENO, iov, 3);
(void)w;
}
void naut_log_emit(naut_log_level lvl, const char *file, int line,
const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
emit_v(lvl, file, line, fmt, ap);
va_end(ap);
}
void naut_panic(const char *file, int line, const char *fmt, ...) {
va_list ap; va_start(ap, fmt);
emit_v(NAUT_LOG_ERROR, file, line, fmt, ap);
va_end(ap);
abort();
}

69
src/core/mpmc.c Normal file
View file

@ -0,0 +1,69 @@
#include "naut/mpmc.h"
#include <stdlib.h>
naut_err naut_mpmc_init(naut_mpmc *q, size_t capacity_pow2) {
if (!NAUT_IS_POW2(capacity_pow2)) return NAUT_ERR_INVAL;
q->buffer = aligned_alloc(NAUT_CACHELINE,
capacity_pow2 * sizeof(naut_mpmc_cell));
if (!q->buffer) return NAUT_ERR_NOMEM;
q->mask = capacity_pow2 - 1;
for (size_t i = 0; i < capacity_pow2; i++) {
atomic_store_explicit(&q->buffer[i].seq, i, memory_order_relaxed);
q->buffer[i].data = NULL;
}
atomic_store_explicit(&q->enqueue_pos, 0, memory_order_relaxed);
atomic_store_explicit(&q->dequeue_pos, 0, memory_order_relaxed);
return NAUT_OK;
}
void naut_mpmc_destroy(naut_mpmc *q) {
free(q->buffer);
q->buffer = NULL;
}
bool naut_mpmc_push(naut_mpmc *q, void *p) {
naut_mpmc_cell *cell;
size_t pos = atomic_load_explicit(&q->enqueue_pos, memory_order_relaxed);
for (;;) {
cell = &q->buffer[pos & q->mask];
size_t seq = atomic_load_explicit(&cell->seq, memory_order_acquire);
intptr_t diff = (intptr_t)seq - (intptr_t)pos;
if (diff == 0) {
if (atomic_compare_exchange_weak_explicit(
&q->enqueue_pos, &pos, pos + 1,
memory_order_relaxed, memory_order_relaxed))
break;
} else if (diff < 0) {
return false; /* full */
} else {
pos = atomic_load_explicit(&q->enqueue_pos, memory_order_relaxed);
}
}
cell->data = p;
atomic_store_explicit(&cell->seq, pos + 1, memory_order_release);
return true;
}
bool naut_mpmc_pop(naut_mpmc *q, void **out) {
naut_mpmc_cell *cell;
size_t pos = atomic_load_explicit(&q->dequeue_pos, memory_order_relaxed);
for (;;) {
cell = &q->buffer[pos & q->mask];
size_t seq = atomic_load_explicit(&cell->seq, memory_order_acquire);
intptr_t diff = (intptr_t)seq - (intptr_t)(pos + 1);
if (diff == 0) {
if (atomic_compare_exchange_weak_explicit(
&q->dequeue_pos, &pos, pos + 1,
memory_order_relaxed, memory_order_relaxed))
break;
} else if (diff < 0) {
return false; /* empty */
} else {
pos = atomic_load_explicit(&q->dequeue_pos, memory_order_relaxed);
}
}
*out = cell->data;
atomic_store_explicit(&cell->seq, pos + q->mask + 1, memory_order_release);
return true;
}

142
src/core/worker.c Normal file
View file

@ -0,0 +1,142 @@
#include "naut/worker.h"
#include "naut/mpmc.h"
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <stdlib.h>
#include <sys/eventfd.h>
#include <unistd.h>
struct naut_worker_pool {
naut_mpmc pending;
naut_mpmc completed;
pthread_t *threads;
uint32_t num_threads;
int event_fd;
int cpu_base;
sem_t work; /* counts queued jobs (+ stop tokens at shutdown) */
bool work_ready; /* sem_init succeeded */
_Atomic bool stop;
};
typedef struct {
naut_worker_pool *pool;
uint32_t index;
} worker_arg;
static void pin_thread(int cpu) {
if (cpu < 0) return;
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET((unsigned)cpu, &set);
(void)pthread_setaffinity_np(pthread_self(), sizeof set, &set);
}
static void *worker_main(void *opaque) {
worker_arg arg = *(worker_arg *)opaque;
free(opaque);
naut_worker_pool *pool = arg.pool;
pin_thread(pool->cpu_base < 0 ? -1 :
pool->cpu_base + (int)arg.index);
for (;;) {
/* Block until a job is submitted (or a shutdown token is posted)
* instead of spinning on sched_yield idle workers cost nothing. */
while (sem_wait(&pool->work) != 0 && errno == EINTR) {}
if (atomic_load_explicit(&pool->stop, memory_order_acquire)) break;
void *item = NULL;
if (!naut_mpmc_pop(&pool->pending, &item)) continue;
naut_job *job = item;
job->run(job);
while (!naut_mpmc_push(&pool->completed, job) &&
!atomic_load_explicit(&pool->stop, memory_order_acquire))
sched_yield();
uint64_t one = 1;
while (write(pool->event_fd, &one, sizeof one) < 0 &&
errno == EINTR) {}
}
return NULL;
}
naut_worker_pool *naut_worker_pool_create(uint32_t threads,
size_t queue_capacity,
int cpu_base) {
if (threads == 0 || !NAUT_IS_POW2(queue_capacity)) return NULL;
naut_worker_pool *pool = calloc(1, sizeof(*pool));
if (!pool) return NULL;
pool->event_fd = -1;
pool->cpu_base = cpu_base;
if (naut_mpmc_init(&pool->pending, queue_capacity) != NAUT_OK ||
naut_mpmc_init(&pool->completed, queue_capacity) != NAUT_OK)
goto fail;
if (sem_init(&pool->work, 0, 0) != 0) goto fail;
pool->work_ready = true;
pool->event_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
pool->threads = calloc(threads, sizeof(*pool->threads));
if (pool->event_fd < 0 || !pool->threads) goto fail;
for (uint32_t i = 0; i < threads; i++) {
worker_arg *arg = malloc(sizeof(*arg));
if (!arg) goto fail_threads;
arg->pool = pool;
arg->index = i;
if (pthread_create(&pool->threads[i], NULL, worker_main, arg) != 0) {
free(arg);
goto fail_threads;
}
pool->num_threads++;
}
return pool;
fail_threads:
atomic_store_explicit(&pool->stop, true, memory_order_release);
for (uint32_t i = 0; i < pool->num_threads; i++) sem_post(&pool->work);
for (uint32_t i = 0; i < pool->num_threads; i++)
if (pool->threads[i]) pthread_join(pool->threads[i], NULL);
fail:
if (pool->work_ready) sem_destroy(&pool->work);
if (pool->event_fd >= 0) close(pool->event_fd);
free(pool->threads);
if (pool->pending.buffer) naut_mpmc_destroy(&pool->pending);
if (pool->completed.buffer) naut_mpmc_destroy(&pool->completed);
free(pool);
return NULL;
}
void naut_worker_pool_destroy(naut_worker_pool *pool) {
if (!pool) return;
atomic_store_explicit(&pool->stop, true, memory_order_release);
/* Wake every worker so the blocking sem_wait returns and sees `stop`. */
for (uint32_t i = 0; i < pool->num_threads; i++) sem_post(&pool->work);
for (uint32_t i = 0; i < pool->num_threads; i++)
pthread_join(pool->threads[i], NULL);
sem_destroy(&pool->work);
close(pool->event_fd);
naut_mpmc_destroy(&pool->pending);
naut_mpmc_destroy(&pool->completed);
free(pool->threads);
free(pool);
}
bool naut_worker_submit(naut_worker_pool *pool, naut_job *job) {
if (!pool || !job || !job->run) return false;
if (!naut_mpmc_push(&pool->pending, job)) return false;
sem_post(&pool->work); /* wake one blocked worker */
return true;
}
bool naut_worker_complete(naut_worker_pool *pool, naut_job **job) {
void *item = NULL;
if (!pool || !job || !naut_mpmc_pop(&pool->completed, &item))
return false;
*job = item;
return true;
}
int naut_worker_eventfd(const naut_worker_pool *pool) {
return pool ? pool->event_fd : -1;
}
uint32_t naut_worker_threads(const naut_worker_pool *pool) {
return pool ? pool->num_threads : 0;
}

59
src/crypto/merkle.c Normal file
View file

@ -0,0 +1,59 @@
#include "naut/merkle.h"
#include <stdlib.h>
#include <string.h>
size_t naut_merkle_leaves(const uint8_t *data, size_t len, uint8_t *out) {
size_t n = 0;
size_t off = 0;
do {
size_t chunk = len - off;
if (chunk > NAUT_MERKLE_LEAF) chunk = NAUT_MERKLE_LEAF;
naut_sha256(data + off, chunk, out + n * NAUT_SHA256_LEN);
off += chunk;
n++;
} while (off < len);
return n; /* len==0 => one leaf hashing the empty string */
}
static size_t next_pow2(size_t n) {
size_t p = 1;
while (p < n) p <<= 1;
return p;
}
/* Reduce `count` (power of two) leaf hashes in `nodes` up to a single root.
* Slots [present, count) are assumed to already hold the correct zero-padding
* hash for the leaf level. Operates in place. */
static void reduce(uint8_t *nodes, size_t count) {
while (count > 1) {
for (size_t i = 0; i < count / 2; i++) {
naut_sha256(nodes + (2*i) * NAUT_SHA256_LEN,
2 * NAUT_SHA256_LEN,
nodes + i * NAUT_SHA256_LEN);
}
count /= 2;
}
}
naut_err naut_merkle_root_padded(const uint8_t *leaves, size_t nleaves,
size_t block_count,
uint8_t out[NAUT_SHA256_LEN]) {
if (block_count == 0) block_count = 1;
if (!NAUT_IS_POW2(block_count) || nleaves > block_count) return NAUT_ERR_INVAL;
uint8_t *nodes = calloc(block_count, NAUT_SHA256_LEN); /* zero-filled pad */
if (!nodes) return NAUT_ERR_NOMEM;
if (nleaves) memcpy(nodes, leaves, nleaves * NAUT_SHA256_LEN);
/* slots [nleaves, block_count) stay all-zero: the v2 zero leaf hash */
reduce(nodes, block_count);
memcpy(out, nodes, NAUT_SHA256_LEN);
free(nodes);
return NAUT_OK;
}
naut_err naut_merkle_root(const uint8_t *leaves, size_t nleaves,
uint8_t out[NAUT_SHA256_LEN]) {
size_t bc = nleaves ? next_pow2(nleaves) : 1;
return naut_merkle_root_padded(leaves, nleaves, bc, out);
}

34
src/crypto/rc4.c Normal file
View file

@ -0,0 +1,34 @@
#include "naut/rc4.h"
void naut_rc4_init(naut_rc4 *c, const void *key, size_t keylen, size_t drop) {
const uint8_t *k = key;
for (int i = 0; i < 256; i++) c->s[i] = (uint8_t)i;
uint8_t j = 0;
for (int i = 0; i < 256; i++) {
j = (uint8_t)(j + c->s[i] + k[i % keylen]);
uint8_t t = c->s[i]; c->s[i] = c->s[j]; c->s[j] = t;
}
c->i = 0; c->j = 0;
if (drop) {
/* discard `drop` keystream bytes */
uint8_t scratch[256];
while (drop) {
size_t n = drop < sizeof scratch ? drop : sizeof scratch;
for (size_t x = 0; x < n; x++) scratch[x] = 0;
naut_rc4_xor(c, scratch, n);
drop -= n;
}
}
}
void naut_rc4_xor(naut_rc4 *c, void *buf, size_t len) {
uint8_t *p = buf;
uint8_t i = c->i, j = c->j;
for (size_t n = 0; n < len; n++) {
i = (uint8_t)(i + 1);
j = (uint8_t)(j + c->s[i]);
uint8_t t = c->s[i]; c->s[i] = c->s[j]; c->s[j] = t;
p[n] ^= c->s[(uint8_t)(c->s[i] + c->s[j])];
}
c->i = i; c->j = j;
}

72
src/crypto/sha1.c Normal file
View file

@ -0,0 +1,72 @@
#include "naut/hash.h"
#include <string.h>
static inline uint32_t rol(uint32_t x, int n) { return (x << n) | (x >> (32 - n)); }
static void sha1_block(uint32_t h[5], const uint8_t *p, size_t nblocks) {
for (size_t b = 0; b < nblocks; b++, p += 64) {
uint32_t w[80];
for (int i = 0; i < 16; i++)
w[i] = ((uint32_t)p[i*4] << 24) | ((uint32_t)p[i*4+1] << 16) |
((uint32_t)p[i*4+2] << 8) | (uint32_t)p[i*4+3];
for (int i = 16; i < 80; i++)
w[i] = rol(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
uint32_t a = h[0], bb = h[1], c = h[2], d = h[3], e = h[4];
for (int i = 0; i < 80; i++) {
uint32_t f, k;
if (i < 20) { f = (bb & c) | (~bb & d); k = 0x5A827999; }
else if (i < 40) { f = bb ^ c ^ d; k = 0x6ED9EBA1; }
else if (i < 60) { f = (bb & c) | (bb & d) | (c & d); k = 0x8F1BBCDC; }
else { f = bb ^ c ^ d; k = 0xCA62C1D6; }
uint32_t t = rol(a, 5) + f + e + k + w[i];
e = d; d = c; c = rol(bb, 30); bb = a; a = t;
}
h[0] += a; h[1] += bb; h[2] += c; h[3] += d; h[4] += e;
}
}
void naut_sha1_init(naut_sha1_ctx *c) {
c->h[0] = 0x67452301; c->h[1] = 0xEFCDAB89; c->h[2] = 0x98BADCFE;
c->h[3] = 0x10325476; c->h[4] = 0xC3D2E1F0;
c->len = 0; c->used = 0;
}
void naut_sha1_update(naut_sha1_ctx *c, const void *data, size_t len) {
const uint8_t *p = data;
c->len += len;
if (c->used) {
size_t need = 64 - c->used;
size_t take = len < need ? len : need;
memcpy(c->block + c->used, p, take);
c->used += take; p += take; len -= take;
if (c->used == 64) { sha1_block(c->h, c->block, 1); c->used = 0; }
}
if (len >= 64) {
size_t nb = len / 64;
sha1_block(c->h, p, nb);
p += nb * 64; len -= nb * 64;
}
if (len) { memcpy(c->block, p, len); c->used = len; }
}
void naut_sha1_final(naut_sha1_ctx *c, uint8_t out[NAUT_SHA1_LEN]) {
uint64_t bits = c->len * 8;
uint8_t pad = 0x80;
naut_sha1_update(c, &pad, 1);
uint8_t zero = 0;
while (c->used != 56) naut_sha1_update(c, &zero, 1);
uint8_t lenbe[8];
for (int i = 0; i < 8; i++) lenbe[i] = (uint8_t)(bits >> (56 - i*8));
naut_sha1_update(c, lenbe, 8);
for (int i = 0; i < 5; i++) {
out[i*4] = (uint8_t)(c->h[i] >> 24);
out[i*4+1] = (uint8_t)(c->h[i] >> 16);
out[i*4+2] = (uint8_t)(c->h[i] >> 8);
out[i*4+3] = (uint8_t)(c->h[i]);
}
}
void naut_sha1(const void *data, size_t len, uint8_t out[NAUT_SHA1_LEN]) {
naut_sha1_ctx c; naut_sha1_init(&c); naut_sha1_update(&c, data, len); naut_sha1_final(&c, out);
}

202
src/crypto/sha256.c Normal file
View file

@ -0,0 +1,202 @@
#include "naut/hash.h"
#include <string.h>
#include <stdlib.h>
#if defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#define NAUT_HAVE_SHANI 1
#endif
static const uint32_t K[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
};
#define ROR(x,n) (((x) >> (n)) | ((x) << (32 - (n))))
static void sha256_scalar(uint32_t s[8], const uint8_t *p, size_t nblocks) {
for (size_t b = 0; b < nblocks; b++, p += 64) {
uint32_t w[64];
for (int i = 0; i < 16; i++)
w[i] = ((uint32_t)p[i*4]<<24)|((uint32_t)p[i*4+1]<<16)|((uint32_t)p[i*4+2]<<8)|p[i*4+3];
for (int i = 16; i < 64; i++) {
uint32_t s0 = ROR(w[i-15],7) ^ ROR(w[i-15],18) ^ (w[i-15] >> 3);
uint32_t s1 = ROR(w[i-2],17) ^ ROR(w[i-2],19) ^ (w[i-2] >> 10);
w[i] = w[i-16] + s0 + w[i-7] + s1;
}
uint32_t a=s[0],bb=s[1],c=s[2],d=s[3],e=s[4],f=s[5],g=s[6],h=s[7];
for (int i = 0; i < 64; i++) {
uint32_t S1 = ROR(e,6) ^ ROR(e,11) ^ ROR(e,25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t t1 = h + S1 + ch + K[i] + w[i];
uint32_t S0 = ROR(a,2) ^ ROR(a,13) ^ ROR(a,22);
uint32_t maj = (a & bb) ^ (a & c) ^ (bb & c);
uint32_t t2 = S0 + maj;
h=g; g=f; f=e; e=d+t1; d=c; c=bb; bb=a; a=t1+t2;
}
s[0]+=a;s[1]+=bb;s[2]+=c;s[3]+=d;s[4]+=e;s[5]+=f;s[6]+=g;s[7]+=h;
}
}
#ifdef NAUT_HAVE_SHANI
__attribute__((target("sha,sse4.1,ssse3")))
static void sha256_shani(uint32_t state[8], const uint8_t *data, size_t nblocks) {
__m128i STATE0, STATE1, MSG, TMP, MSG0, MSG1, MSG2, MSG3, ABEF, CDGH;
const __m128i MASK = _mm_set_epi64x(0x0c0d0e0f08090a0bULL, 0x0405060700010203ULL);
TMP = _mm_loadu_si128((const __m128i*)&state[0]);
STATE1 = _mm_loadu_si128((const __m128i*)&state[4]);
TMP = _mm_shuffle_epi32(TMP, 0xB1); /* CDAB */
STATE1 = _mm_shuffle_epi32(STATE1, 0x1B); /* EFGH */
STATE0 = _mm_alignr_epi8(TMP, STATE1, 8); /* ABEF */
STATE1 = _mm_blend_epi16(STATE1, TMP, 0xF0); /* CDGH */
for (size_t n = 0; n < nblocks; n++, data += 64) {
ABEF = STATE0; CDGH = STATE1;
MSG0 = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)(data+0)), MASK);
MSG = _mm_add_epi32(MSG0, _mm_set_epi64x(0xE9B5DBA5B5C0FBCFULL,0x71374491428A2F98ULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
MSG1 = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)(data+16)), MASK);
MSG = _mm_add_epi32(MSG1, _mm_set_epi64x(0xAB1C5ED5923F82A4ULL,0x59F111F13956C25BULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
MSG0 = _mm_sha256msg1_epu32(MSG0, MSG1);
MSG2 = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)(data+32)), MASK);
MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(0x550C7DC3243185BEULL,0x12835B01D807AA98ULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
MSG1 = _mm_sha256msg1_epu32(MSG1, MSG2);
MSG3 = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i*)(data+48)), MASK);
MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(0xC19BF1749BDC06A7ULL,0x80DEB1FE72BE5D74ULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
TMP = _mm_alignr_epi8(MSG3, MSG2, 4);
MSG0 = _mm_sha256msg2_epu32(_mm_add_epi32(MSG0, TMP), MSG3);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
MSG2 = _mm_sha256msg1_epu32(MSG2, MSG3);
/* rounds 16..63: 12 near-identical message-schedule steps */
/* Call sites pass the K pair in (low64, high64) order, matching how the
* rounds 0-15 blocks above are written; emit set(high, low). */
#define RND4(Ma, Mb, Mc, Md, KL, KH) \
MSG = _mm_add_epi32(Ma, _mm_set_epi64x(KH, KL)); \
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG); \
TMP = _mm_alignr_epi8(Ma, Md, 4); \
Mb = _mm_sha256msg2_epu32(_mm_add_epi32(Mb, TMP), Ma); \
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E)); \
Mc = _mm_sha256msg1_epu32(Mc, Ma);
RND4(MSG0, MSG1, MSG3, MSG3, 0xEFBE4786E49B69C1ULL, 0x240CA1CC0FC19DC6ULL);
RND4(MSG1, MSG2, MSG0, MSG0, 0x4A7484AA2DE92C6FULL, 0x76F988DA5CB0A9DCULL);
RND4(MSG2, MSG3, MSG1, MSG1, 0xA831C66D983E5152ULL, 0xBF597FC7B00327C8ULL);
RND4(MSG3, MSG0, MSG2, MSG2, 0xD5A79147C6E00BF3ULL, 0x1429296706CA6351ULL);
RND4(MSG0, MSG1, MSG3, MSG3, 0x2E1B213827B70A85ULL, 0x53380D134D2C6DFCULL);
RND4(MSG1, MSG2, MSG0, MSG0, 0x766A0ABB650A7354ULL, 0x92722C8581C2C92EULL);
RND4(MSG2, MSG3, MSG1, MSG1, 0xA81A664BA2BFE8A1ULL, 0xC76C51A3C24B8B70ULL);
RND4(MSG3, MSG0, MSG2, MSG2, 0xD6990624D192E819ULL, 0x106AA070F40E3585ULL);
RND4(MSG0, MSG1, MSG3, MSG3, 0x1E376C0819A4C116ULL, 0x34B0BCB52748774CULL);
RND4(MSG1, MSG2, MSG0, MSG0, 0x4ED8AA4A391C0CB3ULL, 0x682E6FF35B9CCA4FULL);
#undef RND4
/* rounds 56..63 (no more message scheduling) */
MSG = _mm_add_epi32(MSG2, _mm_set_epi64x(0x8CC7020884C87814ULL,0x78A5636F748F82EEULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
TMP = _mm_alignr_epi8(MSG2, MSG1, 4);
MSG3 = _mm_sha256msg2_epu32(_mm_add_epi32(MSG3, TMP), MSG2);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
MSG = _mm_add_epi32(MSG3, _mm_set_epi64x(0xC67178F2BEF9A3F7ULL,0xA4506CEB90BEFFFAULL));
STATE1 = _mm_sha256rnds2_epu32(STATE1, STATE0, MSG);
STATE0 = _mm_sha256rnds2_epu32(STATE0, STATE1, _mm_shuffle_epi32(MSG,0x0E));
STATE0 = _mm_add_epi32(STATE0, ABEF);
STATE1 = _mm_add_epi32(STATE1, CDGH);
}
TMP = _mm_shuffle_epi32(STATE0, 0x1B); /* FEBA */
STATE1 = _mm_shuffle_epi32(STATE1, 0xB1); /* DCHG */
STATE0 = _mm_blend_epi16(TMP, STATE1, 0xF0); /* DCBA */
STATE1 = _mm_alignr_epi8(STATE1, TMP, 8); /* ABEF */
_mm_storeu_si128((__m128i*)&state[0], STATE0);
_mm_storeu_si128((__m128i*)&state[4], STATE1);
}
#endif /* NAUT_HAVE_SHANI */
typedef void (*compress_fn)(uint32_t[8], const uint8_t *, size_t);
static compress_fn g_compress;
static const char *g_backend = "scalar";
static compress_fn select_compress(void) {
#ifdef NAUT_HAVE_SHANI
if (!getenv("NAUT_NO_SHANI") && __builtin_cpu_supports("sha")) {
g_backend = "sha-ni";
return sha256_shani;
}
#endif
g_backend = "scalar";
return sha256_scalar;
}
NAUT_INLINE compress_fn compress(void) {
compress_fn f = __atomic_load_n(&g_compress, __ATOMIC_RELAXED);
if (NAUT_UNLIKELY(!f)) {
f = select_compress();
__atomic_store_n(&g_compress, f, __ATOMIC_RELAXED);
}
return f;
}
const char *naut_sha256_backend(void) { (void)compress(); return g_backend; }
void naut_sha256_init(naut_sha256_ctx *c) {
static const uint32_t iv[8] = {
0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,
0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19};
memcpy(c->h, iv, sizeof iv);
c->len = 0; c->used = 0;
}
void naut_sha256_update(naut_sha256_ctx *c, const void *data, size_t len) {
const uint8_t *p = data;
compress_fn f = compress();
c->len += len;
if (c->used) {
size_t need = 64 - c->used, take = len < need ? len : need;
memcpy(c->block + c->used, p, take);
c->used += take; p += take; len -= take;
if (c->used == 64) { f(c->h, c->block, 1); c->used = 0; }
}
if (len >= 64) { size_t nb = len/64; f(c->h, p, nb); p += nb*64; len -= nb*64; }
if (len) { memcpy(c->block, p, len); c->used = len; }
}
void naut_sha256_final(naut_sha256_ctx *c, uint8_t out[NAUT_SHA256_LEN]) {
uint64_t bits = c->len * 8;
uint8_t pad = 0x80, zero = 0;
naut_sha256_update(c, &pad, 1);
while (c->used != 56) naut_sha256_update(c, &zero, 1);
uint8_t lenbe[8];
for (int i = 0; i < 8; i++) lenbe[i] = (uint8_t)(bits >> (56 - i*8));
naut_sha256_update(c, lenbe, 8);
for (int i = 0; i < 8; i++) {
out[i*4] = (uint8_t)(c->h[i] >> 24);
out[i*4+1] = (uint8_t)(c->h[i] >> 16);
out[i*4+2] = (uint8_t)(c->h[i] >> 8);
out[i*4+3] = (uint8_t)(c->h[i]);
}
}
void naut_sha256(const void *data, size_t len, uint8_t out[NAUT_SHA256_LEN]) {
naut_sha256_ctx c; naut_sha256_init(&c); naut_sha256_update(&c, data, len);
naut_sha256_final(&c, out);
}

227
src/dht/dht.c Normal file
View file

@ -0,0 +1,227 @@
#include "naut/dht.h"
#include "naut/bencode.h"
#include <stdlib.h>
#include <string.h>
static naut_err finish(naut_bc_writer *w, uint8_t **out, size_t *out_len) {
if (w->err != NAUT_OK) {
naut_err e = w->err;
naut_bc_w_free(w);
return e;
}
*out = w->buf;
*out_len = w->len;
w->buf = NULL;
naut_bc_w_free(w);
return NAUT_OK;
}
static bool valid_common(const uint8_t *tx, size_t tx_len,
const uint8_t id[20], uint8_t **out, size_t *out_len) {
return tx && tx_len > 0 && tx_len <= 8 && id && out && out_len;
}
naut_err naut_dht_build_ping(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
uint8_t **out, size_t *out_len) {
if (!valid_common(tx, tx_len, id, out, out_len)) return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, "ping");
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
static naut_err build_target_query(const char *query, const char *target_key,
const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t target[20],
uint8_t **out, size_t *out_len) {
if (!query || !target_key || !target ||
!valid_common(tx, tx_len, id, out, out_len)) return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_cstr(&w, target_key); naut_bc_w_bytes(&w, target, 20);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, query);
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
naut_err naut_dht_build_find_node(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t target[20],
uint8_t **out, size_t *out_len) {
return build_target_query("find_node", "target", tx, tx_len, id, target,
out, out_len);
}
naut_err naut_dht_build_get_peers(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t info_hash[20],
uint8_t **out, size_t *out_len) {
return build_target_query("get_peers", "info_hash", tx, tx_len, id,
info_hash, out, out_len);
}
naut_err naut_dht_build_announce_peer(const uint8_t *tx, size_t tx_len,
const uint8_t id[20],
const uint8_t info_hash[20],
uint16_t port, bool implied_port,
const void *token, size_t token_len,
uint8_t **out, size_t *out_len) {
if (!valid_common(tx, tx_len, id, out, out_len) || !info_hash ||
!token || token_len == 0 || token_len > 64 || (!implied_port && port == 0))
return NAUT_ERR_INVAL;
naut_bc_writer w; naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "a"); naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "id"); naut_bc_w_bytes(&w, id, 20);
naut_bc_w_cstr(&w, "implied_port"); naut_bc_w_int(&w, implied_port ? 1 : 0);
naut_bc_w_cstr(&w, "info_hash"); naut_bc_w_bytes(&w, info_hash, 20);
naut_bc_w_cstr(&w, "port"); naut_bc_w_int(&w, port);
naut_bc_w_cstr(&w, "token"); naut_bc_w_bytes(&w, token, token_len);
naut_bc_w_end(&w);
naut_bc_w_cstr(&w, "q"); naut_bc_w_cstr(&w, "announce_peer");
naut_bc_w_cstr(&w, "t"); naut_bc_w_bytes(&w, tx, tx_len);
naut_bc_w_cstr(&w, "y"); naut_bc_w_cstr(&w, "q");
naut_bc_w_end(&w);
return finish(&w, out, out_len);
}
static naut_err parse_nodes(const uint8_t *p, size_t n,
naut_dht_node **out, size_t *count) {
if (n % 26 != 0 || n / 26 > NAUT_DHT_MAX_NODES) return NAUT_ERR_PROTO;
size_t num = n / 26;
naut_dht_node *nodes = calloc(num ? num : 1, sizeof(*nodes));
if (!nodes) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < num; i++) {
const uint8_t *entry = p + i * 26;
memcpy(nodes[i].id, entry, 20);
memcpy(nodes[i].ip, entry + 20, 4);
nodes[i].port = ((uint16_t)entry[24] << 8) | entry[25];
if (nodes[i].port == 0) {
free(nodes);
return NAUT_ERR_PROTO;
}
}
*out = nodes;
*count = num;
return NAUT_OK;
}
static bool peer_duplicate(const naut_peer_addr *peers, size_t n,
const naut_peer_addr *candidate) {
for (size_t i = 0; i < n; i++)
if (peers[i].port == candidate->port &&
memcmp(peers[i].ip, candidate->ip, 4) == 0)
return true;
return false;
}
static naut_err parse_values(const naut_bc *values,
naut_peer_addr **out, size_t *count) {
if (!values || values->type != NAUT_BC_LIST ||
values->v.list.count > NAUT_DHT_MAX_PEERS) return NAUT_ERR_PROTO;
naut_peer_addr *peers = calloc(values->v.list.count ? values->v.list.count : 1,
sizeof(*peers));
if (!peers) return NAUT_ERR_NOMEM;
size_t num = 0;
for (size_t i = 0; i < values->v.list.count; i++) {
const uint8_t *p; size_t n;
if (!naut_bc_get_str(naut_bc_list_at(values, i), &p, &n) || n != 6) {
free(peers);
return NAUT_ERR_PROTO;
}
naut_peer_addr peer;
memcpy(peer.ip, p, 4);
peer.port = ((uint16_t)p[4] << 8) | p[5];
if (peer.port && !peer_duplicate(peers, num, &peer))
peers[num++] = peer;
}
*out = peers;
*count = num;
return NAUT_OK;
}
naut_err naut_dht_parse_response(const uint8_t *data, size_t len,
naut_dht_response *out) {
if (!data || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err err = naut_bc_parse(data, len, &doc);
if (err != NAUT_OK) return err;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *p; size_t n;
if (!root || root->type != NAUT_BC_DICT ||
!naut_bc_get_str(naut_bc_dict_get(root, "t"), &p, &n) ||
n == 0 || n > sizeof out->transaction) {
err = NAUT_ERR_PROTO;
goto done;
}
memcpy(out->transaction, p, n);
out->transaction_len = n;
const naut_bc *y = naut_bc_dict_get(root, "y");
if (naut_bc_str_eq(y, "e")) {
const naut_bc *e = naut_bc_dict_get(root, "e");
int64_t code;
if (!e || e->type != NAUT_BC_LIST || e->v.list.count < 1 ||
!naut_bc_get_int(naut_bc_list_at(e, 0), &code)) {
err = NAUT_ERR_PROTO;
goto done;
}
out->type = NAUT_DHT_ERROR;
out->error_code = (int)code;
goto done;
}
if (!naut_bc_str_eq(y, "r")) {
err = NAUT_ERR_PROTO;
goto done;
}
out->type = NAUT_DHT_RESPONSE;
const naut_bc *r = naut_bc_dict_get(root, "r");
if (!r || r->type != NAUT_BC_DICT) {
err = NAUT_ERR_PROTO;
goto done;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "id"), &p, &n)) {
if (n != 20) { err = NAUT_ERR_PROTO; goto done; }
memcpy(out->id, p, 20);
out->has_id = true;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "token"), &p, &n)) {
if (n == 0 || n > sizeof out->token) { err = NAUT_ERR_PROTO; goto done; }
memcpy(out->token, p, n);
out->token_len = n;
}
if (naut_bc_get_str(naut_bc_dict_get(r, "nodes"), &p, &n)) {
err = parse_nodes(p, n, &out->nodes, &out->num_nodes);
if (err != NAUT_OK) goto done;
}
const naut_bc *values = naut_bc_dict_get(r, "values");
if (values) {
err = parse_values(values, &out->peers, &out->num_peers);
if (err != NAUT_OK) goto done;
}
done:
naut_bc_free(doc);
if (err != NAUT_OK) naut_dht_response_free(out);
return err;
}
void naut_dht_response_free(naut_dht_response *response) {
if (!response) return;
free(response->nodes);
free(response->peers);
memset(response, 0, sizeof(*response));
}

154
src/dht/fetch.c Normal file
View file

@ -0,0 +1,154 @@
#include "naut/dht.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
typedef struct {
struct sockaddr_in addr;
bool queried;
} candidate;
static bool parse_endpoint(const char *text, struct sockaddr_in *out) {
const char *colon = strrchr(text, ':');
if (!colon || colon == text) return false;
char host[256], port[16];
size_t host_len = (size_t)(colon - text);
size_t port_len = strlen(colon + 1);
if (host_len >= sizeof host || port_len == 0 || port_len >= sizeof port)
return false;
memcpy(host, text, host_len); host[host_len] = 0;
memcpy(port, colon + 1, port_len + 1);
struct addrinfo hints, *result = NULL;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
if (getaddrinfo(host, port, &hints, &result) != 0) return false;
memcpy(out, result->ai_addr, sizeof(*out));
freeaddrinfo(result);
return true;
}
static bool same_addr(const struct sockaddr_in *a, const struct sockaddr_in *b) {
return a->sin_port == b->sin_port && a->sin_addr.s_addr == b->sin_addr.s_addr;
}
static bool add_candidate(candidate *v, size_t *n, const struct sockaddr_in *addr) {
if (addr->sin_port == 0) return true;
for (size_t i = 0; i < *n; i++)
if (same_addr(&v[i].addr, addr)) return true;
if (*n == NAUT_DHT_MAX_NODES) return false;
v[*n].addr = *addr;
v[*n].queried = false;
(*n)++;
return true;
}
static bool add_peer(naut_peer_addr *v, size_t *n, const naut_peer_addr *peer) {
for (size_t i = 0; i < *n; i++)
if (v[i].port == peer->port && memcmp(v[i].ip, peer->ip, 4) == 0)
return true;
if (*n == NAUT_DHT_MAX_PEERS) return false;
v[(*n)++] = *peer;
return true;
}
static void node_id(uint8_t id[20]) {
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
size_t done = 0;
while (done < 20) {
ssize_t n = read(fd, id + done, 20 - done);
if (n <= 0) break;
done += (size_t)n;
}
close(fd);
if (done == 20) return;
}
for (size_t i = 0; i < 20; i++) id[i] = (uint8_t)rand();
}
naut_err naut_dht_get_peers(const char *const *bootstrap, size_t num_bootstrap,
const uint8_t info_hash[20],
naut_peer_addr **peers, size_t *num_peers) {
if (!bootstrap || num_bootstrap == 0 || !info_hash || !peers || !num_peers)
return NAUT_ERR_INVAL;
*peers = NULL; *num_peers = 0;
candidate nodes[NAUT_DHT_MAX_NODES];
size_t node_count = 0;
for (size_t i = 0; i < num_bootstrap; i++) {
struct sockaddr_in addr;
if (parse_endpoint(bootstrap[i], &addr))
add_candidate(nodes, &node_count, &addr);
}
if (node_count == 0) return NAUT_ERR_INVAL;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return NAUT_ERR_IO;
naut_peer_addr found[NAUT_DHT_MAX_PEERS];
size_t found_count = 0;
uint8_t id[20];
node_id(id);
uint16_t tx_counter = 1;
size_t queries = 0;
while (queries < 64 && found_count < NAUT_DHT_MAX_PEERS) {
size_t index = SIZE_MAX;
for (size_t i = 0; i < node_count; i++)
if (!nodes[i].queried) { index = i; break; }
if (index == SIZE_MAX) break;
nodes[index].queried = true;
queries++;
uint8_t tx[2] = { (uint8_t)(tx_counter >> 8), (uint8_t)tx_counter };
tx_counter++;
uint8_t *query = NULL; size_t query_len = 0;
if (naut_dht_build_get_peers(tx, sizeof tx, id, info_hash,
&query, &query_len) != NAUT_OK)
continue;
ssize_t sent = sendto(fd, query, query_len, 0,
(struct sockaddr *)&nodes[index].addr,
sizeof(nodes[index].addr));
free(query);
if (sent < 0) continue;
struct pollfd pfd = { .fd = fd, .events = POLLIN };
if (poll(&pfd, 1, 1000) <= 0) continue;
uint8_t packet[65536];
ssize_t received = recv(fd, packet, sizeof packet, 0);
if (received <= 0) continue;
naut_dht_response response;
if (naut_dht_parse_response(packet, (size_t)received, &response) != NAUT_OK)
continue;
if (response.transaction_len != sizeof tx ||
memcmp(response.transaction, tx, sizeof tx) != 0 ||
response.type != NAUT_DHT_RESPONSE) {
naut_dht_response_free(&response);
continue;
}
for (size_t i = 0; i < response.num_peers; i++)
add_peer(found, &found_count, &response.peers[i]);
for (size_t i = 0; i < response.num_nodes; i++) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
memcpy(&addr.sin_addr, response.nodes[i].ip, 4);
addr.sin_port = htons(response.nodes[i].port);
add_candidate(nodes, &node_count, &addr);
}
naut_dht_response_free(&response);
}
close(fd);
if (found_count == 0) return NAUT_ERR_EMPTY;
naut_peer_addr *result = malloc(found_count * sizeof(*result));
if (!result) return NAUT_ERR_NOMEM;
memcpy(result, found, found_count * sizeof(*result));
*peers = result;
*num_peers = found_count;
return NAUT_OK;
}

116
src/metainfo/magnet.c Normal file
View file

@ -0,0 +1,116 @@
#include "naut/metainfo.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
static int hexval(int c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static bool hex_decode(const char *s, size_t slen, uint8_t *out, size_t outlen) {
if (slen != outlen * 2) return false;
for (size_t i = 0; i < outlen; i++) {
int hi = hexval(s[i*2]), lo = hexval(s[i*2+1]);
if (hi < 0 || lo < 0) return false;
out[i] = (uint8_t)((hi << 4) | lo);
}
return true;
}
/* RFC 4648 base32 (no padding needed for the 32-char btih form -> 20 bytes) */
static bool base32_decode(const char *s, size_t slen, uint8_t *out, size_t outlen) {
static const char *A = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
uint32_t buf = 0; int bits = 0; size_t o = 0;
for (size_t i = 0; i < slen; i++) {
char c = (char)toupper((unsigned char)s[i]);
const char *pos = strchr(A, c);
if (!pos || c == 0) return false;
buf = (buf << 5) | (uint32_t)(pos - A);
bits += 5;
if (bits >= 8) {
bits -= 8;
if (o >= outlen) return false;
out[o++] = (uint8_t)((buf >> bits) & 0xff);
}
}
return o == outlen;
}
/* in-place percent-decode of a query-component (also '+' -> space) */
static char *url_decode(const char *s, size_t n) {
char *out = malloc(n + 1);
if (!out) return NULL;
size_t o = 0;
for (size_t i = 0; i < n; i++) {
if (s[i] == '%' && i + 2 < n) {
int hi = hexval(s[i+1]), lo = hexval(s[i+2]);
if (hi >= 0 && lo >= 0) { out[o++] = (char)((hi << 4) | lo); i += 2; continue; }
}
out[o++] = (s[i] == '+') ? ' ' : s[i];
}
out[o] = 0;
return out;
}
static void set_xt(naut_magnet *m, const char *val) {
/* urn:btih:<hex40|base32_32> or urn:btmh:1220<hex64> */
if (!strncmp(val, "urn:btih:", 9)) {
const char *h = val + 9; size_t n = strlen(h);
if (n == 40 && hex_decode(h, 40, m->infohash_v1, 20)) m->has_v1 = true;
else if (n == 32 && base32_decode(h, 32, m->infohash_v1, 20)) m->has_v1 = true;
} else if (!strncmp(val, "urn:btmh:", 9)) {
const char *h = val + 9;
/* multihash: 0x12 = sha2-256, 0x20 = length 32 -> prefix "1220" */
if (strlen(h) == 68 && !strncmp(h, "1220", 4) &&
hex_decode(h + 4, 64, m->infohash_v2, 32))
m->has_v2 = true;
}
}
naut_err naut_magnet_parse(const char *uri, naut_magnet *out) {
memset(out, 0, sizeof(*out));
if (!uri || strncmp(uri, "magnet:?", 8) != 0) return NAUT_ERR_INVAL;
const char *q = uri + 8;
size_t tcap = 0;
while (*q) {
const char *amp = strchr(q, '&');
size_t plen = amp ? (size_t)(amp - q) : strlen(q);
const char *eq = memchr(q, '=', plen);
if (eq) {
size_t klen = (size_t)(eq - q);
const char *vstart = eq + 1;
size_t vlen = plen - klen - 1;
char *val = url_decode(vstart, vlen);
if (val) {
if (klen == 2 && !strncmp(q, "xt", 2)) {
set_xt(out, val);
} else if (klen == 2 && !strncmp(q, "dn", 2)) {
free(out->name); out->name = val; val = NULL;
} else if (klen == 2 && !strncmp(q, "tr", 2)) {
if (out->num_trackers == tcap) {
tcap = tcap ? tcap * 2 : 4;
out->trackers = realloc(out->trackers, tcap * sizeof(char *));
}
out->trackers[out->num_trackers++] = val; val = NULL;
}
free(val);
}
}
if (!amp) break;
q = amp + 1;
}
if (!out->has_v1 && !out->has_v2) { naut_magnet_free(out); return NAUT_ERR_PROTO; }
return NAUT_OK;
}
void naut_magnet_free(naut_magnet *m) {
if (!m) return;
free(m->name);
for (size_t i = 0; i < m->num_trackers; i++) free(m->trackers[i]);
free(m->trackers);
memset(m, 0, sizeof(*m));
}

289
src/metainfo/metainfo.c Normal file
View file

@ -0,0 +1,289 @@
#include "naut/metainfo.h"
#include "naut/bencode.h"
#include "naut/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* The doc + a copy of the source bytes are kept alive in `_owned` so that the
* zero-copy piece-hash slice remains valid for the life of the metainfo. */
typedef struct {
naut_bc_doc *doc;
uint8_t *src;
} owned;
static char *dup_cstr(const uint8_t *p, size_t n) {
char *s = malloc(n + 1);
if (!s) return NULL;
memcpy(s, p, n); s[n] = 0;
return s;
}
/* collect a single announce string or an announce-list (list of tiers) */
static void collect_trackers(const naut_bc *root, naut_metainfo *mi) {
size_t cap = 0;
const naut_bc *al = naut_bc_dict_get(root, "announce-list");
if (al && al->type == NAUT_BC_LIST) {
for (size_t t = 0; t < al->v.list.count; t++) {
const naut_bc *tier = naut_bc_list_at(al, t);
if (!tier || tier->type != NAUT_BC_LIST) continue;
for (size_t u = 0; u < tier->v.list.count; u++) {
const naut_bc *url = naut_bc_list_at(tier, u);
const uint8_t *p; size_t n;
if (!naut_bc_get_str(url, &p, &n)) continue;
if (mi->num_trackers == cap) {
cap = cap ? cap * 2 : 8;
mi->trackers = realloc(mi->trackers, cap * sizeof(char *));
}
mi->trackers[mi->num_trackers++] = dup_cstr(p, n);
}
}
}
if (mi->num_trackers == 0) {
const uint8_t *p; size_t n;
if (naut_bc_get_str(naut_bc_dict_get(root, "announce"), &p, &n)) {
mi->trackers = malloc(sizeof(char *));
mi->trackers[mi->num_trackers++] = dup_cstr(p, n);
}
}
}
/* v1 file list: single-file (info.length) or multi-file (info.files[]) */
static naut_err collect_files_v1(const naut_bc *info, naut_metainfo *mi) {
const uint8_t *np = NULL; size_t nn = 0;
if (naut_bc_get_str(naut_bc_dict_get(info, "name"), &np, &nn))
mi->name = dup_cstr(np, nn);
else
mi->name = dup_cstr((const uint8_t *)"unnamed", 7);
int64_t single_len;
const naut_bc *files = naut_bc_dict_get(info, "files");
if (naut_bc_get_int(naut_bc_dict_get(info, "length"), &single_len)) {
mi->files = calloc(1, sizeof(naut_file));
if (!mi->files) return NAUT_ERR_NOMEM;
mi->files[0].path = dup_cstr((const uint8_t *)mi->name, strlen(mi->name));
mi->files[0].length = single_len;
mi->num_files = 1;
mi->total_length = single_len;
} else if (files && files->type == NAUT_BC_LIST) {
mi->files = calloc(files->v.list.count, sizeof(naut_file));
if (!mi->files) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < files->v.list.count; i++) {
const naut_bc *f = naut_bc_list_at(files, i);
int64_t flen = 0;
naut_bc_get_int(naut_bc_dict_get(f, "length"), &flen);
char joined[4096]; size_t jl = 0;
/* BEP-47 padding file (attr contains 'p'): it occupies the flat byte
* space for v2 piece alignment but is not real content. Keep it in
* the storage layout (offsets stay correct) but route it out of the
* content tree to a root-level .pad/ path. */
const uint8_t *attr; size_t attrn;
bool is_pad = false;
if (naut_bc_get_str(naut_bc_dict_get(f, "attr"), &attr, &attrn))
for (size_t a = 0; a < attrn; a++) if (attr[a] == 'p') is_pad = true;
if (is_pad) {
jl = (size_t)snprintf(joined, sizeof joined, ".pad/%zu", i);
mi->files[i].path = dup_cstr((const uint8_t *)joined, jl);
mi->files[i].length = flen;
mi->total_length += flen;
mi->num_files++;
continue;
}
/* BEP-3 multi-file layout is <name>/<path...>; root the path at name */
const naut_bc *pth = naut_bc_dict_get(f, "path");
size_t namelen = strlen(mi->name);
if (namelen < sizeof joined - 1) { memcpy(joined, mi->name, namelen); jl = namelen; }
if (pth && pth->type == NAUT_BC_LIST) {
for (size_t k = 0; k < pth->v.list.count; k++) {
const uint8_t *cp; size_t cn;
if (!naut_bc_get_str(naut_bc_list_at(pth, k), &cp, &cn)) continue;
if (jl < sizeof joined - 1) joined[jl++] = '/';
size_t room = sizeof joined - 1 - jl;
if (cn > room) cn = room;
memcpy(joined + jl, cp, cn); jl += cn;
}
}
joined[jl] = 0;
mi->files[i].path = dup_cstr((const uint8_t *)joined, jl);
mi->files[i].length = flen;
mi->total_length += flen;
mi->num_files++;
}
} else {
return NAUT_ERR_PROTO; /* neither length nor files */
}
return NAUT_OK;
}
/* v2 (BEP-52) "file tree": nested dicts; a leaf is a dict with an empty-string
* key mapping to {length, pieces root}. Build '/'-joined paths and sum lengths. */
static void add_file(naut_metainfo *mi, size_t *cap, const char *path, int64_t len) {
if (mi->num_files == *cap) {
*cap = *cap ? *cap * 2 : 8;
mi->files = realloc(mi->files, *cap * sizeof(naut_file));
}
mi->files[mi->num_files].path = dup_cstr((const uint8_t *)path, strlen(path));
mi->files[mi->num_files].length = len;
mi->num_files++;
mi->total_length += len;
}
static void walk_tree(const naut_bc *node, naut_metainfo *mi, size_t *cap,
char *prefix, size_t plen) {
if (!node || node->type != NAUT_BC_DICT) return;
for (size_t i = 0; i < node->v.dict.count; i++) {
const naut_bc_pair *pr = &node->v.dict.pairs[i];
if (pr->kn == 0) { /* leaf: this prefix is a file */
int64_t flen = 0;
naut_bc_get_int(naut_bc_dict_get(pr->val, "length"), &flen);
prefix[plen] = 0;
add_file(mi, cap, prefix, flen);
continue;
}
char sub[4096];
memcpy(sub, prefix, plen);
size_t sl = plen;
if (sl && sl < sizeof sub - 1) sub[sl++] = '/';
size_t room = sizeof sub - 1 - sl;
size_t cn = pr->kn < room ? pr->kn : room;
memcpy(sub + sl, pr->kp, cn); sl += cn;
walk_tree(pr->val, mi, cap, sub, sl);
}
}
static void collect_files_v2(const naut_bc *info, naut_metainfo *mi) {
const uint8_t *np; size_t nn;
if (!mi->name && naut_bc_get_str(naut_bc_dict_get(info, "name"), &np, &nn))
mi->name = dup_cstr(np, nn);
const naut_bc *tree = naut_bc_dict_get(info, "file tree");
if (!tree) return;
size_t cap = 0;
char prefix[4096];
walk_tree(tree, mi, &cap, prefix, 0);
}
naut_err naut_metainfo_parse(const uint8_t *data, size_t len, naut_metainfo *out) {
memset(out, 0, sizeof(*out));
owned *o = calloc(1, sizeof(owned));
if (!o) return NAUT_ERR_NOMEM;
/* own a copy so slices outlive the caller's buffer */
o->src = malloc(len ? len : 1);
if (!o->src) { free(o); return NAUT_ERR_NOMEM; }
memcpy(o->src, data, len);
naut_err e = naut_bc_parse(o->src, len, &o->doc);
if (e != NAUT_OK) { free(o->src); free(o); return e; }
const naut_bc *root = naut_bc_root(o->doc);
const naut_bc *info = naut_bc_dict_get(root, "info");
if (!info || info->type != NAUT_BC_DICT) {
naut_bc_free(o->doc); free(o->src); free(o);
return NAUT_ERR_PROTO;
}
/* info-hashes over the raw info-dict bytes */
const naut_bc *pieces = naut_bc_dict_get(info, "pieces");
int64_t meta_ver = 0;
naut_bc_get_int(naut_bc_dict_get(info, "meta version"), &meta_ver);
if (pieces && pieces->type == NAUT_BC_STR) { /* v1 / hybrid */
naut_sha1(info->raw, info->raw_len, out->infohash_v1);
out->has_v1 = true;
}
if (meta_ver == 2) { /* v2 / hybrid */
naut_sha256(info->raw, info->raw_len, out->infohash_v2);
out->has_v2 = true;
}
if (!out->has_v1 && !out->has_v2) {
naut_bc_free(o->doc); free(o->src); free(o);
return NAUT_ERR_PROTO;
}
naut_bc_get_int(naut_bc_dict_get(info, "piece length"), &out->piece_length);
if (out->has_v1) {
if (pieces->v.str.n % NAUT_SHA1_LEN != 0) {
naut_bc_free(o->doc); free(o->src); free(o);
return NAUT_ERR_PROTO;
}
out->num_pieces = (uint32_t)(pieces->v.str.n / NAUT_SHA1_LEN);
out->piece_hashes = pieces->v.str.p; /* slice into o->src */
}
if (out->has_v1) {
e = collect_files_v1(info, out);
if (e != NAUT_OK) { out->_owned = o; naut_metainfo_free(out); return e; }
} else {
collect_files_v2(info, out); /* v2-only: walk the file tree */
}
collect_trackers(root, out);
out->_owned = o;
return NAUT_OK;
}
naut_err naut_metainfo_parse_info(const uint8_t *info, size_t info_len,
const char *const *trackers,
size_t num_trackers,
naut_metainfo *out) {
if (!info || info_len == 0 || !out ||
(num_trackers && !trackers))
return NAUT_ERR_INVAL;
if (info_len > SIZE_MAX - 8) return NAUT_ERR_RANGE;
uint8_t *torrent = malloc(info_len + 8);
if (!torrent) return NAUT_ERR_NOMEM;
memcpy(torrent, "d4:info", 7);
memcpy(torrent + 7, info, info_len);
torrent[7 + info_len] = 'e';
naut_err e = naut_metainfo_parse(torrent, info_len + 8, out);
free(torrent);
if (e != NAUT_OK) return e;
if (num_trackers) {
out->trackers = calloc(num_trackers, sizeof(*out->trackers));
if (!out->trackers) {
naut_metainfo_free(out);
return NAUT_ERR_NOMEM;
}
for (size_t i = 0; i < num_trackers; i++) {
out->trackers[i] =
dup_cstr((const uint8_t *)trackers[i], strlen(trackers[i]));
if (!out->trackers[i]) {
out->num_trackers = i;
naut_metainfo_free(out);
return NAUT_ERR_NOMEM;
}
}
out->num_trackers = num_trackers;
}
return NAUT_OK;
}
void naut_metainfo_free(naut_metainfo *mi) {
if (!mi) return;
free(mi->name);
for (size_t i = 0; i < mi->num_files; i++) free(mi->files[i].path);
free(mi->files);
for (size_t i = 0; i < mi->num_trackers; i++) free(mi->trackers[i]);
free(mi->trackers);
if (mi->_owned) {
owned *o = mi->_owned;
naut_bc_free(o->doc);
free(o->src);
free(o);
}
memset(mi, 0, sizeof(*mi));
}
void naut_infohash_v1_hex(const naut_metainfo *mi, char out[41]) {
static const char *hx = "0123456789abcdef";
for (int i = 0; i < NAUT_SHA1_LEN; i++) {
out[i*2] = hx[mi->infohash_v1[i] >> 4];
out[i*2+1] = hx[mi->infohash_v1[i] & 15];
}
out[40] = 0;
}

296
src/peer/extension.c Normal file
View file

@ -0,0 +1,296 @@
#include "naut/extension.h"
#include "naut/bencode.h"
#include "naut/peer.h"
#include <stdlib.h>
#include <string.h>
static void wr32(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v;
}
static naut_err frame(uint8_t ext_id, const uint8_t *payload, size_t payload_len,
uint8_t **out, size_t *out_len) {
if (!out || !out_len || payload_len > UINT32_MAX - 2) return NAUT_ERR_INVAL;
size_t n = 6 + payload_len;
uint8_t *buf = malloc(n);
if (!buf) return NAUT_ERR_NOMEM;
wr32(buf, (uint32_t)(2 + payload_len));
buf[4] = NAUT_MSG_EXTENDED;
buf[5] = ext_id;
if (payload_len) memcpy(buf + 6, payload, payload_len);
*out = buf;
*out_len = n;
return NAUT_OK;
}
naut_err naut_ext_build_handshake(uint8_t ut_metadata_id, uint8_t ut_pex_id,
uint32_t metadata_size, uint16_t port,
uint8_t **out, size_t *out_len) {
naut_bc_writer w;
naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "m");
naut_bc_w_dict_begin(&w);
if (ut_metadata_id) {
naut_bc_w_cstr(&w, "ut_metadata");
naut_bc_w_int(&w, ut_metadata_id);
}
if (ut_pex_id) {
naut_bc_w_cstr(&w, "ut_pex");
naut_bc_w_int(&w, ut_pex_id);
}
naut_bc_w_end(&w);
if (metadata_size) {
naut_bc_w_cstr(&w, "metadata_size");
naut_bc_w_int(&w, metadata_size);
}
if (port) {
naut_bc_w_cstr(&w, "p");
naut_bc_w_int(&w, port);
}
naut_bc_w_cstr(&w, "reqq");
naut_bc_w_int(&w, 256);
naut_bc_w_cstr(&w, "v");
naut_bc_w_cstr(&w, "Naut/0.1");
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
naut_err e = frame(0, w.buf, w.len, out, out_len);
naut_bc_w_free(&w);
return e;
}
static bool get_u32(const naut_bc *dict, const char *key, uint32_t *out) {
int64_t v;
if (!naut_bc_get_int(naut_bc_dict_get(dict, key), &v) ||
v < 0 || v > UINT32_MAX) return false;
*out = (uint32_t)v;
return true;
}
naut_err naut_ext_parse_handshake(const uint8_t *payload, size_t len,
naut_ext_handshake *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(payload, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
if (!root || root->type != NAUT_BC_DICT) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
const naut_bc *m = naut_bc_dict_get(root, "m");
uint32_t v;
if (m && m->type != NAUT_BC_DICT) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
if (m && get_u32(m, "ut_metadata", &v) && v <= UINT8_MAX)
out->ut_metadata = (uint8_t)v;
if (m && get_u32(m, "ut_pex", &v) && v <= UINT8_MAX)
out->ut_pex = (uint8_t)v;
if (get_u32(root, "metadata_size", &v)) {
if (v == 0 || v > NAUT_METADATA_MAX) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->metadata_size = v;
}
if (get_u32(root, "reqq", &v)) out->reqq = v;
if (get_u32(root, "p", &v) && v <= UINT16_MAX) out->port = (uint16_t)v;
naut_bc_free(doc);
return NAUT_OK;
}
naut_err naut_metadata_build(uint8_t ext_id, naut_metadata_type type,
uint32_t piece, uint32_t total_size,
const void *data, size_t data_len,
uint8_t **out, size_t *out_len) {
if (!ext_id || type > NAUT_METADATA_REJECT ||
(type == NAUT_METADATA_DATA && (!data || total_size == 0)) ||
(type != NAUT_METADATA_DATA && data_len != 0))
return NAUT_ERR_INVAL;
naut_bc_writer w;
naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
naut_bc_w_cstr(&w, "msg_type"); naut_bc_w_int(&w, type);
naut_bc_w_cstr(&w, "piece"); naut_bc_w_int(&w, piece);
if (type == NAUT_METADATA_DATA) {
naut_bc_w_cstr(&w, "total_size"); naut_bc_w_int(&w, total_size);
}
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
if (data_len > SIZE_MAX - w.len) {
naut_bc_w_free(&w);
return NAUT_ERR_RANGE;
}
size_t payload_len = w.len + data_len;
uint8_t *payload = malloc(payload_len ? payload_len : 1);
if (!payload) {
naut_bc_w_free(&w);
return NAUT_ERR_NOMEM;
}
memcpy(payload, w.buf, w.len);
if (data_len) memcpy(payload + w.len, data, data_len);
naut_err e = frame(ext_id, payload, payload_len, out, out_len);
free(payload);
naut_bc_w_free(&w);
return e;
}
naut_err naut_metadata_parse(const uint8_t *payload, size_t len,
naut_metadata_msg *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
size_t used = 0;
naut_err e = naut_bc_parse_prefix(payload, len, &doc, &used);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
uint32_t type, piece;
if (!root || root->type != NAUT_BC_DICT ||
!get_u32(root, "msg_type", &type) || type > NAUT_METADATA_REJECT ||
!get_u32(root, "piece", &piece)) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->type = (naut_metadata_type)type;
out->piece = piece;
if (out->type == NAUT_METADATA_DATA) {
if (!get_u32(root, "total_size", &out->total_size) ||
out->total_size == 0 || out->total_size > NAUT_METADATA_MAX) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
out->data = payload + used;
out->data_len = len - used;
uint32_t pieces = (out->total_size + NAUT_METADATA_BLOCK - 1) /
NAUT_METADATA_BLOCK;
if (piece >= pieces ||
out->data_len != (piece + 1 < pieces
? NAUT_METADATA_BLOCK
: out->total_size - (size_t)piece * NAUT_METADATA_BLOCK)) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
} else if (used != len) {
naut_bc_free(doc);
return NAUT_ERR_PROTO;
}
naut_bc_free(doc);
return NAUT_OK;
}
static naut_err parse_compact(const uint8_t *p, size_t n,
naut_peer_addr **out, size_t *count) {
if (n % 6 != 0 || n / 6 > 200) return NAUT_ERR_PROTO;
size_t num = n / 6;
naut_peer_addr *v = calloc(num ? num : 1, sizeof(*v));
if (!v) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < num; i++) {
memcpy(v[i].ip, p + i * 6, 4);
v[i].port = ((uint16_t)p[i * 6 + 4] << 8) | p[i * 6 + 5];
if (v[i].port == 0) {
free(v);
return NAUT_ERR_PROTO;
}
}
*out = v;
*count = num;
return NAUT_OK;
}
naut_err naut_pex_parse(const uint8_t *payload, size_t len, naut_pex_msg *out) {
if (!payload || !out) return NAUT_ERR_INVAL;
memset(out, 0, sizeof(*out));
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(payload, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *p; size_t n;
if (!root || root->type != NAUT_BC_DICT) {
e = NAUT_ERR_PROTO;
goto done;
}
if (naut_bc_get_str(naut_bc_dict_get(root, "added"), &p, &n)) {
e = parse_compact(p, n, &out->added, &out->num_added);
if (e != NAUT_OK) goto done;
}
const uint8_t *flags; size_t flags_n;
if (naut_bc_get_str(naut_bc_dict_get(root, "added.f"), &flags, &flags_n)) {
if (flags_n != out->num_added) { e = NAUT_ERR_PROTO; goto done; }
out->added_flags = malloc(flags_n ? flags_n : 1);
if (!out->added_flags) { e = NAUT_ERR_NOMEM; goto done; }
memcpy(out->added_flags, flags, flags_n);
}
if (naut_bc_get_str(naut_bc_dict_get(root, "dropped"), &p, &n)) {
e = parse_compact(p, n, &out->dropped, &out->num_dropped);
if (e != NAUT_OK) goto done;
}
if (out->num_added == 0 && out->num_dropped == 0) e = NAUT_ERR_PROTO;
done:
naut_bc_free(doc);
if (e != NAUT_OK) naut_pex_free(out);
return e;
}
naut_err naut_pex_build(uint8_t ext_id, const naut_pex_msg *msg,
uint8_t **out, size_t *out_len) {
if (!ext_id || !msg || (msg->num_added == 0 && msg->num_dropped == 0) ||
msg->num_added > 200 || msg->num_dropped > 200)
return NAUT_ERR_INVAL;
naut_bc_writer w;
naut_bc_w_init(&w);
naut_bc_w_dict_begin(&w);
if (msg->num_added) {
uint8_t compact[200 * 6];
for (size_t i = 0; i < msg->num_added; i++) {
memcpy(compact + i * 6, msg->added[i].ip, 4);
compact[i * 6 + 4] = (uint8_t)(msg->added[i].port >> 8);
compact[i * 6 + 5] = (uint8_t)msg->added[i].port;
}
naut_bc_w_cstr(&w, "added");
naut_bc_w_bytes(&w, compact, msg->num_added * 6);
if (msg->added_flags) {
naut_bc_w_cstr(&w, "added.f");
naut_bc_w_bytes(&w, msg->added_flags, msg->num_added);
}
}
if (msg->num_dropped) {
uint8_t compact[200 * 6];
for (size_t i = 0; i < msg->num_dropped; i++) {
memcpy(compact + i * 6, msg->dropped[i].ip, 4);
compact[i * 6 + 4] = (uint8_t)(msg->dropped[i].port >> 8);
compact[i * 6 + 5] = (uint8_t)msg->dropped[i].port;
}
naut_bc_w_cstr(&w, "dropped");
naut_bc_w_bytes(&w, compact, msg->num_dropped * 6);
}
naut_bc_w_end(&w);
if (w.err != NAUT_OK) {
naut_err e = w.err;
naut_bc_w_free(&w);
return e;
}
naut_err e = frame(ext_id, w.buf, w.len, out, out_len);
naut_bc_w_free(&w);
return e;
}
void naut_pex_free(naut_pex_msg *msg) {
if (!msg) return;
free(msg->added);
free(msg->added_flags);
free(msg->dropped);
memset(msg, 0, sizeof(*msg));
}

235
src/peer/metadata.c Normal file
View file

@ -0,0 +1,235 @@
#include "naut/extension.h"
#include "naut/hash.h"
#include "naut/peer.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#define EXT_RESERVED 0x0000000000100000ULL
static bool send_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = send(fd, p, len, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static bool recv_exact(int fd, void *data, size_t len) {
uint8_t *p = data;
while (len) {
ssize_t n = recv(fd, p, len, 0);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static int connect_peer(const naut_peer_addr *peer) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(peer->port);
memcpy(&addr.sin_addr, peer->ip, sizeof peer->ip);
if (connect(fd, (struct sockaddr *)&addr, sizeof addr) != 0) {
close(fd);
return -1;
}
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);
struct timeval timeout = { .tv_sec = 10 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout);
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof timeout);
return fd;
}
naut_err naut_metadata_fetch(const naut_peer_addr *peer,
const uint8_t info_hash[20],
const uint8_t peer_id[20],
uint8_t **info, size_t *info_len) {
if (!peer || !info_hash || !peer_id || !info || !info_len)
return NAUT_ERR_INVAL;
*info = NULL;
*info_len = 0;
int fd = connect_peer(peer);
if (fd < 0) return NAUT_ERR_IO;
naut_err result = NAUT_ERR_IO;
uint8_t *metadata = NULL, *frame = NULL, *buffer = NULL;
bool *received = NULL;
uint8_t handshake[NAUT_HANDSHAKE_LEN];
naut_peer_handshake_build(handshake, info_hash, peer_id, EXT_RESERVED);
if (!send_all(fd, handshake, sizeof handshake) ||
!recv_exact(fd, handshake, sizeof handshake))
goto done;
uint8_t remote_hash[20], remote_id[20];
uint64_t reserved = 0;
if (!naut_peer_handshake_parse(handshake, remote_hash, remote_id,
&reserved) ||
memcmp(remote_hash, info_hash, 20) != 0 ||
(reserved & EXT_RESERVED) == 0) {
result = NAUT_ERR_PROTO;
goto done;
}
size_t frame_len = 0;
result = naut_ext_build_handshake(NAUT_EXT_UT_METADATA, NAUT_EXT_UT_PEX,
0, 0, &frame, &frame_len);
if (result != NAUT_OK || !send_all(fd, frame, frame_len)) {
result = NAUT_ERR_IO;
goto done;
}
free(frame);
frame = NULL;
size_t cap = 128 * 1024, len = 0;
buffer = malloc(cap);
if (!buffer) {
result = NAUT_ERR_NOMEM;
goto done;
}
naut_ext_handshake remote_ext = {0};
uint32_t piece_count = 0, received_count = 0;
while (!metadata || received_count < piece_count) {
if (len == cap) {
if (cap >= NAUT_METADATA_MAX + (1u << 20)) {
result = NAUT_ERR_PROTO;
goto done;
}
size_t next_cap = cap * 2;
uint8_t *next = realloc(buffer, next_cap);
if (!next) {
result = NAUT_ERR_NOMEM;
goto done;
}
buffer = next;
cap = next_cap;
}
ssize_t n = recv(fd, buffer + len, cap - len, 0);
if (n < 0) {
if (errno == EINTR) continue;
result = NAUT_ERR_IO;
goto done;
}
if (n == 0) {
result = NAUT_ERR_IO;
goto done;
}
len += (size_t)n;
size_t pos = 0;
for (;;) {
naut_msg msg;
int consumed = naut_peer_msg_parse(buffer + pos, len - pos, &msg);
if (consumed == 0) break;
if (consumed < 0) {
result = NAUT_ERR_PROTO;
goto done;
}
pos += (size_t)consumed;
if (msg.type != NAUT_MSG_EXTENDED || msg.payload_len < 1)
continue;
uint8_t ext_id = msg.payload[0];
const uint8_t *payload = msg.payload + 1;
size_t payload_len = msg.payload_len - 1;
if (ext_id == 0) {
result = naut_ext_parse_handshake(payload, payload_len,
&remote_ext);
if (result != NAUT_OK || remote_ext.ut_metadata == 0 ||
remote_ext.metadata_size == 0) {
result = NAUT_ERR_PROTO;
goto done;
}
if (!metadata) {
metadata = malloc(remote_ext.metadata_size);
piece_count =
(remote_ext.metadata_size + NAUT_METADATA_BLOCK - 1) /
NAUT_METADATA_BLOCK;
received = calloc(piece_count, sizeof(*received));
if (!metadata || !received) {
result = NAUT_ERR_NOMEM;
goto done;
}
for (uint32_t piece = 0; piece < piece_count; piece++) {
result = naut_metadata_build(
remote_ext.ut_metadata, NAUT_METADATA_REQUEST,
piece, 0, NULL, 0, &frame, &frame_len);
if (result != NAUT_OK ||
!send_all(fd, frame, frame_len)) {
result = NAUT_ERR_IO;
goto done;
}
free(frame);
frame = NULL;
}
}
} else if (metadata &&
(ext_id == NAUT_EXT_UT_METADATA ||
ext_id == remote_ext.ut_metadata)) {
naut_metadata_msg metadata_msg;
result = naut_metadata_parse(payload, payload_len,
&metadata_msg);
if (result != NAUT_OK ||
metadata_msg.type == NAUT_METADATA_REJECT ||
metadata_msg.total_size != remote_ext.metadata_size ||
metadata_msg.piece >= piece_count) {
result = NAUT_ERR_PROTO;
goto done;
}
if (metadata_msg.type == NAUT_METADATA_DATA &&
!received[metadata_msg.piece]) {
memcpy(metadata +
(size_t)metadata_msg.piece * NAUT_METADATA_BLOCK,
metadata_msg.data, metadata_msg.data_len);
received[metadata_msg.piece] = true;
received_count++;
}
}
}
memmove(buffer, buffer + pos, len - pos);
len -= pos;
}
uint8_t digest[20];
naut_sha1(metadata, remote_ext.metadata_size, digest);
if (memcmp(digest, info_hash, 20) != 0) {
result = NAUT_ERR_PROTO;
goto done;
}
*info = metadata;
*info_len = remote_ext.metadata_size;
metadata = NULL;
result = NAUT_OK;
done:
close(fd);
free(metadata);
free(received);
free(frame);
free(buffer);
return result;
}

466
src/peer/mse.c Normal file
View file

@ -0,0 +1,466 @@
#include "naut/mse.h"
#include "naut/hash.h"
#include <errno.h>
#include <openssl/bn.h>
#include <openssl/rand.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define MSE_PAD_MAX 512
#define MSE_CRYPTO_RC4 2u
static const char DH_PRIME_HEX[] =
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC"
"74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF2"
"5F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A63A3621000000"
"0000090563";
static void wr16(uint8_t *p, uint16_t value) {
p[0] = (uint8_t)(value >> 8);
p[1] = (uint8_t)value;
}
static void wr32(uint8_t *p, uint32_t value) {
p[0] = (uint8_t)(value >> 24);
p[1] = (uint8_t)(value >> 16);
p[2] = (uint8_t)(value >> 8);
p[3] = (uint8_t)value;
}
static uint16_t rd16(const uint8_t *p) {
return ((uint16_t)p[0] << 8) | p[1];
}
static uint32_t rd32(const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | p[3];
}
static void hash_parts(const char label[4],
const uint8_t *first, size_t first_len,
const uint8_t *second, size_t second_len,
uint8_t out[20]) {
naut_sha1_ctx sha;
naut_sha1_init(&sha);
naut_sha1_update(&sha, label, 4);
naut_sha1_update(&sha, first, first_len);
if (second && second_len) naut_sha1_update(&sha, second, second_len);
naut_sha1_final(&sha, out);
}
static void init_rc4(const uint8_t secret[NAUT_MSE_DH_LEN],
const uint8_t info_hash[20],
naut_mse_stream *stream) {
uint8_t key_a[20], key_b[20];
hash_parts("keyA", secret, NAUT_MSE_DH_LEN, info_hash, 20, key_a);
hash_parts("keyB", secret, NAUT_MSE_DH_LEN, info_hash, 20, key_b);
naut_rc4_init(&stream->send, key_a, sizeof key_a, 1024);
naut_rc4_init(&stream->recv, key_b, sizeof key_b, 1024);
memset(key_a, 0, sizeof key_a);
memset(key_b, 0, sizeof key_b);
}
/* ---- sans-IO handshake state machine ------------------------------------- */
enum {
PH_RECV_PUBKEY, /* waiting for the peer's 96-byte DH public key */
PH_SYNC_VC, /* scanning past PadB for the encrypted VC */
PH_RECV_SELECT, /* crypto_select + len(PadD) */
PH_RECV_PAD, /* PadD bytes (discarded) */
PH_RECV_HS, /* the peer's encrypted BitTorrent handshake */
};
struct naut_mse_handshake {
int phase;
naut_err err;
bool done;
uint8_t info_hash[20];
uint8_t peer_id[NAUT_PEERID_LEN];
uint64_t reserved;
/* DH state retained until the shared secret is computed. */
BN_CTX *ctx;
BIGNUM *prime;
BIGNUM *priv;
naut_mse_stream stream;
uint8_t expected_vc[8];
size_t vc_scanned;
size_t pad_remaining;
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN];
uint8_t out[256];
size_t out_len, out_off;
uint8_t in[1024];
size_t in_len;
};
static void dh_free(naut_mse_handshake *h) {
BN_CTX_free(h->ctx); h->ctx = NULL;
BN_free(h->prime); h->prime = NULL;
BN_clear_free(h->priv); h->priv = NULL;
}
/* Generate our private key and public value, writing the 96-byte public key
* into the outgoing buffer. Retains prime/priv/ctx for dh_complete(). */
static naut_err dh_begin(naut_mse_handshake *h) {
naut_err result = NAUT_ERR_IO;
BIGNUM *generator = BN_new();
BIGNUM *local = BN_new();
h->ctx = BN_CTX_new();
h->priv = BN_new();
if (!generator || !local || !h->ctx || !h->priv ||
!BN_hex2bn(&h->prime, DH_PRIME_HEX) || !BN_set_word(generator, 2))
goto done;
do {
if (!BN_rand_range(h->priv, h->prime)) goto done;
} while (BN_cmp(h->priv, generator) < 0);
if (!BN_mod_exp(local, generator, h->priv, h->prime, h->ctx) ||
BN_bn2binpad(local, h->out, NAUT_MSE_DH_LEN) != NAUT_MSE_DH_LEN)
goto done;
h->out_len = NAUT_MSE_DH_LEN;
h->out_off = 0;
result = NAUT_OK;
done:
BN_free(generator);
BN_free(local);
if (result != NAUT_OK) dh_free(h);
return result;
}
/* Validate the peer's public key and derive the shared secret. */
static naut_err dh_complete(naut_mse_handshake *h, const uint8_t remote_bytes[96],
uint8_t secret[NAUT_MSE_DH_LEN]) {
naut_err result = NAUT_ERR_IO;
BIGNUM *remote = BN_new();
BIGNUM *shared = BN_new();
BIGNUM *limit = BN_new();
BIGNUM *two = BN_new();
if (!remote || !shared || !limit || !two ||
!BN_bin2bn(remote_bytes, NAUT_MSE_DH_LEN, remote) ||
!BN_set_word(two, 2) || !BN_copy(limit, h->prime) ||
!BN_sub_word(limit, 1))
goto done;
if (BN_cmp(remote, two) < 0 || BN_cmp(remote, limit) >= 0) {
result = NAUT_ERR_PROTO;
goto done;
}
if (!BN_mod_exp(shared, remote, h->priv, h->prime, h->ctx) ||
BN_bn2binpad(shared, secret, NAUT_MSE_DH_LEN) != NAUT_MSE_DH_LEN)
goto done;
result = NAUT_OK;
done:
BN_free(remote);
BN_clear_free(shared);
BN_free(limit);
BN_free(two);
return result;
}
naut_mse_handshake *naut_mse_handshake_begin(
const uint8_t info_hash[20],
const uint8_t peer_id[NAUT_PEERID_LEN],
uint64_t reserved) {
if (!info_hash || !peer_id) return NULL;
naut_mse_handshake *h = calloc(1, sizeof(*h));
if (!h) return NULL;
memcpy(h->info_hash, info_hash, 20);
memcpy(h->peer_id, peer_id, NAUT_PEERID_LEN);
h->reserved = reserved;
h->phase = PH_RECV_PUBKEY;
if (dh_begin(h) != NAUT_OK) {
naut_mse_handshake_free(h);
return NULL;
}
return h;
}
void naut_mse_handshake_free(naut_mse_handshake *h) {
if (!h) return;
dh_free(h);
/* keystream state is sensitive; scrub before release */
memset(h, 0, sizeof(*h));
free(h);
}
static void consume(naut_mse_handshake *h, size_t n) {
memmove(h->in, h->in + n, h->in_len - n);
h->in_len -= n;
}
/* Build req1/req2 + encrypted offer (VC, crypto_provide, PadC, IA) into out. */
static void build_request(naut_mse_handshake *h, const uint8_t secret[96]) {
uint8_t req1[20], req2[20], req3[20];
hash_parts("req1", secret, NAUT_MSE_DH_LEN, NULL, 0, req1);
hash_parts("req2", h->info_hash, 20, NULL, 0, req2);
hash_parts("req3", secret, NAUT_MSE_DH_LEN, NULL, 0, req3);
for (size_t i = 0; i < sizeof req2; i++) req2[i] ^= req3[i];
init_rc4(secret, h->info_hash, &h->stream);
uint8_t *p = h->out;
memcpy(p, req1, 20);
memcpy(p + 20, req2, 20);
p += 40;
uint8_t *offer = p; /* VC(8) crypto_provide(4) padlen(2) ialen(2) IA */
memset(offer, 0, 8);
wr32(offer + 8, MSE_CRYPTO_RC4);
wr16(offer + 12, 0);
wr16(offer + 14, NAUT_HANDSHAKE_LEN);
naut_peer_handshake_build(offer + 16, h->info_hash, h->peer_id, h->reserved);
size_t offer_len = 16 + NAUT_HANDSHAKE_LEN;
naut_rc4_xor(&h->stream.send, offer, offer_len);
h->out_len = 40 + offer_len;
h->out_off = 0;
/* expected_vc = our recv keystream applied to 8 zero bytes at position 0,
* without advancing the real recv state (we resync on it). */
naut_rc4 probe = h->stream.recv;
uint8_t vc[8] = {0};
naut_rc4_xor(&probe, vc, sizeof vc);
memcpy(h->expected_vc, vc, sizeof vc);
h->vc_scanned = 0;
}
static void advance(naut_mse_handshake *h) {
for (;;) {
switch (h->phase) {
case PH_RECV_PUBKEY: {
if (h->in_len < NAUT_MSE_DH_LEN) return;
uint8_t secret[NAUT_MSE_DH_LEN];
naut_err e = dh_complete(h, h->in, secret);
if (e != NAUT_OK) { h->err = e; return; }
consume(h, NAUT_MSE_DH_LEN);
dh_free(h); /* DH no longer needed */
build_request(h, secret);
memset(secret, 0, sizeof secret);
h->phase = PH_SYNC_VC;
return; /* out now holds req+offer: NEED_WRITE */
}
case PH_SYNC_VC: {
while (h->in_len >= sizeof h->expected_vc) {
if (memcmp(h->in, h->expected_vc, sizeof h->expected_vc) == 0) {
uint8_t vc[8];
memcpy(vc, h->in, sizeof vc);
naut_rc4_xor(&h->stream.recv, vc, sizeof vc);
static const uint8_t zero8[8] = {0};
if (memcmp(vc, zero8, sizeof vc) != 0) {
h->err = NAUT_ERR_PROTO;
return;
}
consume(h, sizeof vc);
h->phase = PH_RECV_SELECT;
break;
}
consume(h, 1);
if (++h->vc_scanned > MSE_PAD_MAX) {
h->err = NAUT_ERR_PROTO;
return;
}
}
if (h->phase == PH_SYNC_VC) return; /* need more bytes */
continue;
}
case PH_RECV_SELECT: {
if (h->in_len < 6) return;
uint8_t hdr[6];
memcpy(hdr, h->in, sizeof hdr);
naut_rc4_xor(&h->stream.recv, hdr, sizeof hdr);
consume(h, sizeof hdr);
if (rd32(hdr) != MSE_CRYPTO_RC4) { h->err = NAUT_ERR_PROTO; return; }
h->pad_remaining = rd16(hdr + 4);
if (h->pad_remaining > MSE_PAD_MAX) { h->err = NAUT_ERR_PROTO; return; }
h->phase = PH_RECV_PAD;
continue;
}
case PH_RECV_PAD: {
if (h->pad_remaining > 0) {
size_t n = h->pad_remaining < h->in_len ? h->pad_remaining
: h->in_len;
if (n == 0) return;
naut_rc4_xor(&h->stream.recv, h->in, n); /* advance keystream */
consume(h, n);
h->pad_remaining -= n;
if (h->pad_remaining > 0) return;
}
h->phase = PH_RECV_HS;
continue;
}
case PH_RECV_HS: {
if (h->in_len < NAUT_HANDSHAKE_LEN) return;
memcpy(h->remote_handshake, h->in, NAUT_HANDSHAKE_LEN);
naut_rc4_xor(&h->stream.recv, h->remote_handshake, NAUT_HANDSHAKE_LEN);
consume(h, NAUT_HANDSHAKE_LEN);
uint8_t remote_hash[20], remote_id[20];
if (!naut_peer_handshake_parse(h->remote_handshake, remote_hash,
remote_id, NULL) ||
memcmp(remote_hash, h->info_hash, 20) != 0) {
h->err = NAUT_ERR_PROTO;
return;
}
h->stream.active = true;
h->done = true;
return;
}
default:
h->err = NAUT_ERR_PROTO;
return;
}
}
}
naut_mse_hs_status naut_mse_handshake_status(const naut_mse_handshake *h) {
if (!h || h->err != NAUT_OK) return NAUT_MSE_HS_ERROR;
if (h->done) return NAUT_MSE_HS_DONE;
if (h->out_off < h->out_len) return NAUT_MSE_HS_NEED_WRITE;
return NAUT_MSE_HS_NEED_READ;
}
size_t naut_mse_handshake_pull(naut_mse_handshake *h, uint8_t *buf, size_t cap) {
if (!h || !buf) return 0;
size_t avail = h->out_len - h->out_off;
size_t n = avail < cap ? avail : cap;
if (n) {
memcpy(buf, h->out + h->out_off, n);
h->out_off += n;
if (h->out_off == h->out_len) h->out_len = h->out_off = 0;
}
return n;
}
naut_mse_hs_status naut_mse_handshake_feed(naut_mse_handshake *h,
const uint8_t *data, size_t len,
size_t *consumed) {
if (consumed) *consumed = 0;
if (!h) return NAUT_MSE_HS_ERROR;
if (h->err == NAUT_OK && !h->done && data && len) {
size_t space = sizeof h->in - h->in_len;
size_t take = len < space ? len : space;
memcpy(h->in + h->in_len, data, take);
h->in_len += take;
if (consumed) *consumed = take;
advance(h);
}
return naut_mse_handshake_status(h);
}
naut_err naut_mse_handshake_finish(naut_mse_handshake *h,
naut_mse_stream *stream,
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]) {
if (!h || !stream || !remote_handshake) return NAUT_ERR_INVAL;
if (h->err != NAUT_OK) return h->err;
if (!h->done) return NAUT_ERR_AGAIN;
*stream = h->stream;
memcpy(remote_handshake, h->remote_handshake, NAUT_HANDSHAKE_LEN);
return NAUT_OK;
}
/* ---- blocking I/O helpers + convenience wrapper -------------------------- */
static bool raw_send_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = send(fd, p, len, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
static bool raw_recv_exact(int fd, void *data, size_t len) {
uint8_t *p = data;
while (len) {
ssize_t n = recv(fd, p, len, 0);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
len -= (size_t)n;
}
return true;
}
naut_err naut_mse_client_handshake(
int fd,
const uint8_t info_hash[20],
const uint8_t peer_id[NAUT_PEERID_LEN],
uint64_t reserved,
naut_mse_stream *stream,
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]) {
if (fd < 0 || !info_hash || !peer_id || !stream || !remote_handshake)
return NAUT_ERR_INVAL;
memset(stream, 0, sizeof(*stream));
naut_mse_handshake *h =
naut_mse_handshake_begin(info_hash, peer_id, reserved);
if (!h) return NAUT_ERR_NOMEM;
naut_err rc = NAUT_ERR_PROTO;
for (;;) {
naut_mse_hs_status st = naut_mse_handshake_status(h);
if (st == NAUT_MSE_HS_NEED_WRITE) {
uint8_t buf[256];
size_t n;
bool ok = true;
while ((n = naut_mse_handshake_pull(h, buf, sizeof buf)) > 0)
if (!raw_send_all(fd, buf, n)) { ok = false; break; }
if (!ok) { rc = NAUT_ERR_IO; break; }
} else if (st == NAUT_MSE_HS_NEED_READ) {
/* One byte at a time: the handshake is tiny and one-shot, and this
* keeps the wrapper from over-reading into the payload stream. */
uint8_t byte;
if (!raw_recv_exact(fd, &byte, 1)) { rc = NAUT_ERR_IO; break; }
naut_mse_handshake_feed(h, &byte, 1, NULL);
} else if (st == NAUT_MSE_HS_DONE) {
rc = naut_mse_handshake_finish(h, stream, remote_handshake);
break;
} else {
rc = h->err != NAUT_OK ? h->err : NAUT_ERR_PROTO;
break;
}
}
naut_mse_handshake_free(h);
return rc;
}
/* ---- post-handshake stream I/O ------------------------------------------- */
bool naut_mse_send_all(int fd, naut_mse_stream *stream,
const void *data, size_t len) {
if (!stream || !stream->active) return raw_send_all(fd, data, len);
const uint8_t *p = data;
uint8_t block[16 * 1024];
while (len) {
size_t n = len < sizeof block ? len : sizeof block;
memcpy(block, p, n);
naut_rc4_xor(&stream->send, block, n);
if (!raw_send_all(fd, block, n)) return false;
p += n;
len -= n;
}
return true;
}
ssize_t naut_mse_recv(int fd, naut_mse_stream *stream,
void *data, size_t len) {
ssize_t n;
do {
n = recv(fd, data, len, 0);
} while (n < 0 && errno == EINTR);
if (n > 0 && stream && stream->active)
naut_rc4_xor(&stream->recv, data, (size_t)n);
return n;
}

64
src/peer/pipeline.c Normal file
View file

@ -0,0 +1,64 @@
#include "naut/pipeline.h"
#include <math.h>
static uint32_t clamp_depth(const naut_pipeline *p, uint32_t depth) {
if (depth < p->min_depth) return p->min_depth;
if (depth > p->max_depth) return p->max_depth;
return depth;
}
void naut_pipeline_init(naut_pipeline *p, uint32_t block_size,
uint32_t min_depth, uint32_t max_depth,
uint32_t initial_depth) {
if (!p) return;
if (block_size == 0) block_size = NAUT_BLOCK;
if (min_depth == 0) min_depth = 1;
if (max_depth < min_depth) max_depth = min_depth;
p->rtt_seconds = 0;
p->bytes_per_second = 0;
p->last_sample_at = 0;
p->min_depth = min_depth;
p->max_depth = max_depth;
p->block_size = block_size;
p->depth = clamp_depth(p, initial_depth);
}
void naut_pipeline_on_block(naut_pipeline *p, uint32_t bytes,
double sent_at, double received_at) {
if (!p || bytes == 0 || sent_at <= 0 || received_at <= sent_at) return;
double rtt = received_at - sent_at;
if (rtt > 60.0) return;
if (p->rtt_seconds == 0) p->rtt_seconds = rtt;
else p->rtt_seconds = p->rtt_seconds * 0.875 + rtt * 0.125;
double interval = p->last_sample_at > 0
? received_at - p->last_sample_at : rtt;
if (interval <= 0) interval = rtt;
double rate = bytes / interval;
if (p->bytes_per_second == 0) p->bytes_per_second = rate;
else p->bytes_per_second = p->bytes_per_second * 0.8 + rate * 0.2;
p->last_sample_at = received_at;
double blocks = (2.0 * p->bytes_per_second * p->rtt_seconds) /
p->block_size;
uint32_t target = blocks >= UINT32_MAX ? UINT32_MAX :
(uint32_t)ceil(blocks);
target = clamp_depth(p, target);
/* Grow quickly enough to fill a fast path; shrink one eighth at a time so
* transient delayed samples do not collapse the pipe. */
if (target > p->depth) {
uint32_t step = p->depth / 4 + 1;
p->depth = clamp_depth(p, NAUT_MIN(target, p->depth + step));
} else if (target < p->depth) {
uint32_t step = p->depth / 8 + 1;
p->depth = clamp_depth(p, target > p->depth - step
? target : p->depth - step);
}
}
uint32_t naut_pipeline_depth(const naut_pipeline *p) {
return p ? p->depth : 0;
}

113
src/peer/wire.c Normal file
View file

@ -0,0 +1,113 @@
#include "naut/peer.h"
#include <string.h>
static const char PSTR[] = "BitTorrent protocol"; /* 19 bytes */
#define PSTRLEN 19
static inline void wr32(uint8_t *p, uint32_t v) {
p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16);
p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v;
}
static inline uint32_t rd32(const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
void naut_peer_handshake_build(uint8_t out[NAUT_HANDSHAKE_LEN],
const uint8_t infohash[20],
const uint8_t peerid[NAUT_PEERID_LEN],
uint64_t reserved) {
out[0] = PSTRLEN;
memcpy(out + 1, PSTR, PSTRLEN);
for (int i = 0; i < 8; i++) out[20 + i] = (uint8_t)(reserved >> (56 - i*8));
memcpy(out + 28, infohash, 20);
memcpy(out + 48, peerid, 20);
}
bool naut_peer_handshake_parse(const uint8_t in[NAUT_HANDSHAKE_LEN],
uint8_t infohash[20],
uint8_t peerid[NAUT_PEERID_LEN],
uint64_t *reserved) {
if (in[0] != PSTRLEN || memcmp(in + 1, PSTR, PSTRLEN) != 0) return false;
if (reserved) {
uint64_t r = 0;
for (int i = 0; i < 8; i++) r = (r << 8) | in[20 + i];
*reserved = r;
}
memcpy(infohash, in + 28, 20);
memcpy(peerid, in + 48, 20);
return true;
}
int naut_peer_msg_parse(const uint8_t *buf, size_t len, naut_msg *out) {
if (len < 4) return 0;
uint32_t n = rd32(buf);
if (n == 0) { out->type = NAUT_MSG_KEEPALIVE; return 4; } /* keep-alive */
if (n > NAUT_MSG_MAX) return NAUT_ERR_PROTO;
if (len < 4 + (size_t)n) return 0; /* need more */
const uint8_t *p = buf + 4;
uint8_t id = p[0];
const uint8_t *body = p + 1;
uint32_t blen = n - 1;
memset(out, 0, sizeof(*out));
out->type = (naut_msg_type)id;
switch (id) {
case NAUT_MSG_CHOKE: case NAUT_MSG_UNCHOKE:
case NAUT_MSG_INTERESTED: case NAUT_MSG_NOT_INTERESTED:
if (blen != 0) return NAUT_ERR_PROTO;
break;
case NAUT_MSG_HAVE:
if (blen != 4) return NAUT_ERR_PROTO;
out->index = rd32(body);
break;
case NAUT_MSG_BITFIELD:
out->payload = body; out->payload_len = blen;
break;
case NAUT_MSG_REQUEST: case NAUT_MSG_CANCEL:
if (blen != 12) return NAUT_ERR_PROTO;
out->index = rd32(body); out->begin = rd32(body + 4); out->length = rd32(body + 8);
break;
case NAUT_MSG_PIECE:
if (blen < 8) return NAUT_ERR_PROTO;
out->index = rd32(body); out->begin = rd32(body + 4);
out->payload = body + 8; out->payload_len = blen - 8;
out->length = blen - 8;
break;
case NAUT_MSG_PORT:
if (blen != 2) return NAUT_ERR_PROTO;
out->index = ((uint32_t)body[0] << 8) | body[1]; /* port in index */
break;
default:
/* unknown/extended: surface type + raw payload, let caller decide */
out->payload = body; out->payload_len = blen;
break;
}
return (int)(4 + n);
}
size_t naut_peer_keepalive(uint8_t out[4]) { wr32(out, 0); return 4; }
size_t naut_peer_msg_simple(uint8_t out[5], naut_msg_type t) {
wr32(out, 1); out[4] = (uint8_t)t; return 5;
}
size_t naut_peer_msg_have(uint8_t out[9], uint32_t index) {
wr32(out, 5); out[4] = NAUT_MSG_HAVE; wr32(out + 5, index); return 9;
}
size_t naut_peer_msg_request(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length) {
wr32(out, 13); out[4] = NAUT_MSG_REQUEST;
wr32(out + 5, index); wr32(out + 9, begin); wr32(out + 13, length); return 17;
}
size_t naut_peer_msg_cancel(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length) {
wr32(out, 13); out[4] = NAUT_MSG_CANCEL;
wr32(out + 5, index); wr32(out + 9, begin); wr32(out + 13, length); return 17;
}
size_t naut_peer_msg_piece_header(uint8_t out[13], uint32_t index, uint32_t begin, uint32_t block_len) {
wr32(out, 9 + block_len); out[4] = NAUT_MSG_PIECE;
wr32(out + 5, index); wr32(out + 9, begin); return 13;
}
size_t naut_peer_msg_bitfield(uint8_t *out, const uint8_t *bf, size_t nbytes) {
wr32(out, (uint32_t)(1 + nbytes)); out[4] = NAUT_MSG_BITFIELD;
memcpy(out + 5, bf, nbytes); return 5 + nbytes;
}

385
src/piece/piece.c Normal file
View file

@ -0,0 +1,385 @@
#include "naut/piece.h"
#include "naut/bitfield.h"
#include "naut/hash.h"
#include "naut/log.h"
#include <stdlib.h>
#include <string.h>
#define BLK NAUT_BLOCK /* 16 KiB */
#define ENDGAME_BLOCKS 8 /* switch to endgame when this few remain */
#define ENDGAME_COPIES 2 /* at most two peers race a missing block */
/* per-piece in-progress state, lazily allocated and freed on completion */
typedef struct {
uint8_t *recv_bits; /* received block bitmap */
uint8_t *req_count; /* outstanding requests per block */
uint8_t *buf; /* assembly buffer, piece_size bytes */
uint32_t nblocks;
uint32_t nrecv;
bool verifying;
naut_job verify_job;
struct naut_download *download;
uint32_t piece;
uint8_t digest[NAUT_SHA1_LEN];
} pstate;
struct naut_download {
const naut_metainfo *mi;
naut_storage *st;
uint32_t num_pieces;
uint64_t piece_len;
uint64_t total;
naut_bitfield have;
uint32_t *avail; /* [num_pieces] swarm availability count */
pstate **ps; /* [num_pieces] in-progress state or NULL */
uint32_t cur_piece; /* sequential cursor for next_request() */
uint64_t total_blocks, recv_blocks;
uint32_t pieces_done;
uint64_t bytes_done;
bool endgame;
naut_worker_pool *workers;
size_t num_files;
uint32_t *file_first, *file_last, *file_remain;
bool *file_done;
naut_file_complete_cb file_cb;
void *file_cb_ctx;
};
static bool bget(const uint8_t *a, uint32_t i) { return (a[i>>3] >> (i&7)) & 1; }
static void bset(uint8_t *a, uint32_t i) { a[i>>3] |= (uint8_t)(1u << (i&7)); }
static uint64_t piece_size(const naut_download *d, uint32_t p) {
if (p + 1 < d->num_pieces) return d->piece_len;
return d->total - (uint64_t)p * d->piece_len;
}
static uint32_t nblocks(const naut_download *d, uint32_t p) {
return (uint32_t)((piece_size(d, p) + BLK - 1) / BLK);
}
static uint32_t block_len(const naut_download *d, uint32_t p, uint32_t b) {
uint64_t rem = piece_size(d, p) - (uint64_t)b * BLK;
return rem < BLK ? (uint32_t)rem : BLK;
}
static pstate *ensure_ps(naut_download *d, uint32_t p) {
if (d->ps[p]) return d->ps[p];
pstate *s = calloc(1, sizeof(*s));
if (!s) return NULL;
s->nblocks = nblocks(d, p);
s->download = d;
s->piece = p;
size_t bm = (s->nblocks + 7) / 8;
s->recv_bits = calloc(1, bm);
s->req_count = calloc(s->nblocks, sizeof(uint8_t));
size_t alloc_size =
(size_t)NAUT_ALIGN_UP(piece_size(d, p), NAUT_PAGE);
s->buf = aligned_alloc(NAUT_PAGE, alloc_size);
if (!s->recv_bits || !s->req_count || !s->buf) {
free(s->recv_bits); free(s->req_count); free(s->buf); free(s);
return NULL;
}
d->ps[p] = s;
return s;
}
static void free_ps(naut_download *d, uint32_t p) {
pstate *s = d->ps[p];
if (!s) return;
free(s->recv_bits); free(s->req_count); free(s->buf); free(s);
d->ps[p] = NULL;
}
naut_download *naut_download_create(const naut_metainfo *mi, naut_storage *st) {
if (!mi->has_v1 || mi->num_pieces == 0 || mi->piece_length <= 0 ||
mi->total_length <= 0) {
NAUT_ERROR("download: needs a v1/hybrid torrent (SHA-1 pieces)");
return NULL;
}
uint64_t total = (uint64_t)mi->total_length;
uint64_t piece_len = (uint64_t)mi->piece_length;
uint64_t expected_pieces = 1 + (total - 1) / piece_len;
if (expected_pieces != mi->num_pieces || piece_len > UINT32_MAX * (uint64_t)BLK) {
NAUT_ERROR("download: inconsistent piece geometry");
return NULL;
}
naut_download *d = calloc(1, sizeof(*d));
if (!d) return NULL;
d->mi = mi; d->st = st;
d->num_pieces = mi->num_pieces;
d->piece_len = (uint64_t)mi->piece_length;
d->total = (uint64_t)mi->total_length;
d->avail = calloc(d->num_pieces, sizeof(uint32_t));
d->ps = calloc(d->num_pieces, sizeof(pstate *));
if (!d->avail || !d->ps || naut_bitfield_init(&d->have, d->num_pieces) != NAUT_OK) {
naut_download_destroy(d); return NULL;
}
for (uint32_t p = 0; p < d->num_pieces; p++) d->total_blocks += nblocks(d, p);
d->num_files = mi->num_files;
d->file_first = calloc(mi->num_files, sizeof(uint32_t));
d->file_last = calloc(mi->num_files, sizeof(uint32_t));
d->file_remain = calloc(mi->num_files, sizeof(uint32_t));
d->file_done = calloc(mi->num_files, sizeof(bool));
if (mi->num_files && (!d->file_first || !d->file_last || !d->file_remain || !d->file_done)) {
naut_download_destroy(d); return NULL;
}
uint64_t off = 0;
for (size_t f = 0; f < mi->num_files; f++) {
uint64_t flen = (uint64_t)mi->files[f].length;
if (flen == 0) { d->file_first[f] = 1; d->file_last[f] = 0; d->file_done[f] = true; }
else {
d->file_first[f] = (uint32_t)(off / d->piece_len);
d->file_last[f] = (uint32_t)((off + flen - 1) / d->piece_len);
d->file_remain[f] = d->file_last[f] - d->file_first[f] + 1;
}
off += flen;
}
return d;
}
void naut_download_destroy(naut_download *d) {
if (!d) return;
if (d->ps) for (uint32_t p = 0; p < d->num_pieces; p++) free_ps(d, p);
free(d->ps); free(d->avail);
free(d->file_first); free(d->file_last); free(d->file_remain); free(d->file_done);
naut_bitfield_free(&d->have);
free(d);
}
void naut_download_set_worker_pool(naut_download *d, naut_worker_pool *pool) {
if (d) d->workers = pool;
}
void naut_download_set_file_cb(naut_download *d, naut_file_complete_cb cb, void *ctx) {
d->file_cb = cb; d->file_cb_ctx = ctx;
}
bool naut_download_file_complete(const naut_download *d, uint32_t f) {
return f < d->num_files && d->file_done[f];
}
static void notify_files(naut_download *d, uint32_t p) {
size_t lo = 0, hi = d->num_files;
while (lo < hi) { size_t mid = (lo + hi) / 2;
if (d->file_last[mid] < p) lo = mid + 1; else hi = mid; }
for (size_t f = lo; f < d->num_files && d->file_first[f] <= p; f++) {
if (d->file_done[f]) continue;
if (--d->file_remain[f] == 0) {
d->file_done[f] = true;
if (d->file_cb) d->file_cb(d->file_cb_ctx, (uint32_t)f, d->mi->files[f].path);
}
}
}
/* --- availability -------------------------------------------------------- */
void naut_download_inc_avail(naut_download *d, uint32_t p) {
if (p < d->num_pieces) d->avail[p]++;
}
void naut_download_add_bitfield(naut_download *d, const naut_bitfield *bf) {
uint32_t limit = (uint32_t)NAUT_MIN((size_t)d->num_pieces, bf->nbits);
for (uint32_t p = 0; p < limit; p++)
if (naut_bitfield_test(bf, p)) d->avail[p]++;
}
void naut_download_remove_bitfield(naut_download *d, const naut_bitfield *bf) {
uint32_t limit = (uint32_t)NAUT_MIN((size_t)d->num_pieces, bf->nbits);
for (uint32_t p = 0; p < limit; p++)
if (naut_bitfield_test(bf, p) && d->avail[p]) d->avail[p]--;
}
/* --- request selection --------------------------------------------------- */
/* Find the first missing block with no outstanding request. */
static uint32_t first_unreq(const pstate *s) {
if (s->verifying) return UINT32_MAX;
for (uint32_t b = 0; b < s->nblocks; b++)
if (!bget(s->recv_bits, b) && s->req_count[b] == 0) return b;
return UINT32_MAX;
}
static bool hand_out(naut_download *d, uint32_t p, uint32_t b,
uint32_t *index, uint32_t *begin, uint32_t *length) {
d->ps[p]->req_count[b]++;
*index = p; *begin = b * BLK; *length = block_len(d, p, b);
return true;
}
bool naut_download_pick_for_peer(naut_download *d, const naut_bitfield *peer_have,
naut_request_active_cb peer_has_request, void *ctx,
uint32_t *index, uint32_t *begin, uint32_t *length) {
d->endgame = (d->total_blocks - d->recv_blocks) <= ENDGAME_BLOCKS;
/* pass 1: finish an in-progress piece the peer has (reduces fragmentation) */
for (uint32_t p = 0; p < d->num_pieces; p++) {
if (naut_bitfield_test(&d->have, p) || !d->ps[p]) continue;
if (p >= peer_have->nbits || !naut_bitfield_test(peer_have, p)) continue;
uint32_t b = first_unreq(d->ps[p]);
if (b != UINT32_MAX) return hand_out(d, p, b, index, begin, length);
}
/* pass 2: start the rarest new piece the peer has */
uint32_t best = UINT32_MAX, best_av = UINT32_MAX;
for (uint32_t p = 0; p < d->num_pieces; p++) {
if (naut_bitfield_test(&d->have, p) || d->ps[p]) continue;
if (p >= peer_have->nbits || !naut_bitfield_test(peer_have, p) ||
d->avail[p] == 0) continue;
if (d->avail[p] < best_av) { best = p; best_av = d->avail[p]; }
}
if (best != UINT32_MAX) {
if (!ensure_ps(d, best)) return false;
return hand_out(d, best, 0, index, begin, length);
}
/* pass 3: endgame — race each missing block on at most two distinct peers */
if (d->endgame) {
for (uint8_t copies = 1; copies < ENDGAME_COPIES; copies++) {
for (uint32_t p = 0; p < d->num_pieces; p++) {
if (naut_bitfield_test(&d->have, p) ||
p >= peer_have->nbits ||
!naut_bitfield_test(peer_have, p)) continue;
if (!ensure_ps(d, p)) continue;
pstate *s = d->ps[p];
for (uint32_t b = 0; b < s->nblocks; b++) {
uint32_t block_begin = b * BLK;
if (bget(s->recv_bits, b) || s->req_count[b] != copies) continue;
if (peer_has_request &&
peer_has_request(ctx, p, block_begin)) continue;
return hand_out(d, p, b, index, begin, length);
}
}
}
}
return false;
}
bool naut_download_pick(naut_download *d, const naut_bitfield *peer_have,
uint32_t *index, uint32_t *begin, uint32_t *length) {
return naut_download_pick_for_peer(d, peer_have, NULL, NULL,
index, begin, length);
}
void naut_download_unrequest(naut_download *d, uint32_t index, uint32_t begin) {
if (index >= d->num_pieces || !d->ps[index]) return;
uint32_t b = begin / BLK;
if (b < d->ps[index]->nblocks && !bget(d->ps[index]->recv_bits, b) &&
d->ps[index]->req_count[b] != 0)
d->ps[index]->req_count[b]--;
}
/* sequential single-peer convenience (Phase 3 leecher + tests) */
bool naut_download_next_request(naut_download *d,
uint32_t *index, uint32_t *begin, uint32_t *length) {
while (d->cur_piece < d->num_pieces) {
if (naut_bitfield_test(&d->have, d->cur_piece)) { d->cur_piece++; continue; }
pstate *s = ensure_ps(d, d->cur_piece);
if (!s) return false;
uint32_t b = first_unreq(s);
if (b != UINT32_MAX) return hand_out(d, d->cur_piece, b, index, begin, length);
d->cur_piece++;
}
return false;
}
/* --- block ingest -------------------------------------------------------- */
static naut_err finish_verified(naut_download *d, uint32_t p,
const uint8_t digest[NAUT_SHA1_LEN],
bool *done) {
pstate *s = d->ps[p];
uint64_t ps = piece_size(d, p);
if (memcmp(digest,
d->mi->piece_hashes + (size_t)p * NAUT_SHA1_LEN,
NAUT_SHA1_LEN) != 0) {
NAUT_WARN("piece %u failed SHA-1; discarding for re-download", p);
d->recv_blocks -= s->nrecv; /* roll back so it can be refetched */
free_ps(d, p);
return NAUT_ERR_PROTO;
}
naut_err e = naut_storage_write(d->st, (int64_t)p * (int64_t)d->piece_len, s->buf, ps);
if (e != NAUT_OK) return e;
naut_bitfield_set(&d->have, p);
d->pieces_done++;
d->bytes_done += ps;
free_ps(d, p);
*done = true;
notify_files(d, p);
return NAUT_OK;
}
static void verify_job_run(naut_job *job) {
pstate *state = job->context;
naut_sha1(state->buf, piece_size(state->download, state->piece),
state->digest);
job->result = NAUT_OK;
}
naut_err naut_download_poll(naut_download *d, uint32_t *pieces_completed) {
if (!d) return NAUT_ERR_INVAL;
if (pieces_completed) *pieces_completed = 0;
if (!d->workers) return NAUT_OK;
naut_job *job;
naut_err result = NAUT_OK;
while (naut_worker_complete(d->workers, &job)) {
pstate *state = job->context;
uint32_t piece = state->piece;
if (state->download != d || piece >= d->num_pieces ||
d->ps[piece] != state || !state->verifying) {
result = NAUT_ERR_PROTO;
continue;
}
bool done = false;
naut_err e = finish_verified(d, piece, state->digest, &done);
if (e == NAUT_ERR_PROTO) {
/* Hash mismatch already reset the piece for re-download. The
* worker cannot attribute corruption to one peer, so keep the
* torrent alive and let the picker request it again. */
continue;
}
if (e != NAUT_OK) {
result = e;
continue;
}
if (done && pieces_completed) (*pieces_completed)++;
}
return result;
}
naut_err naut_download_on_block(naut_download *d, uint32_t index, uint32_t begin,
const uint8_t *data, uint32_t len, bool *piece_done) {
*piece_done = false;
if (index >= d->num_pieces) return NAUT_ERR_RANGE;
if (naut_bitfield_test(&d->have, index)) return NAUT_OK; /* already complete */
if (begin % BLK != 0) return NAUT_ERR_PROTO;
uint32_t b = begin / BLK;
if (b >= nblocks(d, index) || len != block_len(d, index, b)) return NAUT_ERR_PROTO;
pstate *s = ensure_ps(d, index);
if (!s) return NAUT_ERR_NOMEM;
if (bget(s->recv_bits, b)) return NAUT_OK; /* duplicate, ignore */
memcpy(s->buf + begin, data, len);
bset(s->recv_bits, b);
s->req_count[b] = 0;
s->nrecv++;
d->recv_blocks++;
if (s->nrecv == s->nblocks) {
if (d->workers) {
s->verifying = true;
s->verify_job.run = verify_job_run;
s->verify_job.context = s;
s->verify_job.result = NAUT_ERR_AGAIN;
if (naut_worker_submit(d->workers, &s->verify_job))
return NAUT_OK;
s->verifying = false;
}
uint8_t digest[NAUT_SHA1_LEN];
naut_sha1(s->buf, piece_size(d, index), digest);
return finish_verified(d, index, digest, piece_done);
}
return NAUT_OK;
}
bool naut_download_complete(const naut_download *d) { return d->pieces_done == d->num_pieces; }
bool naut_download_have(const naut_download *d, uint32_t p) { return naut_bitfield_test(&d->have, p); }
bool naut_download_in_endgame(const naut_download *d) { return d->endgame; }
uint32_t naut_download_num_pieces(const naut_download *d) { return d->num_pieces; }
uint32_t naut_download_pieces_done(const naut_download *d) { return d->pieces_done; }
uint64_t naut_download_bytes_done(const naut_download *d) { return d->bytes_done; }

62
src/platform/net.c Normal file
View file

@ -0,0 +1,62 @@
#include "naut/net.h"
#include "naut/log.h"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
static bool setopt(int fd, int level, int opt, int val) {
return setsockopt(fd, level, opt, &val, sizeof(val)) == 0;
}
int naut_net_listen(uint16_t port, int backlog, bool reuseport) {
int fd = socket(AF_INET6, SOCK_STREAM, 0);
if (fd < 0) { NAUT_ERROR("socket: %s", strerror(errno)); return -1; }
setopt(fd, SOL_SOCKET, SO_REUSEADDR, 1);
if (reuseport && !setopt(fd, SOL_SOCKET, SO_REUSEPORT, 1))
NAUT_WARN("SO_REUSEPORT unavailable: %s", strerror(errno));
/* dual-stack v4+v6 */
setopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, 0);
struct sockaddr_in6 a;
memset(&a, 0, sizeof(a));
a.sin6_family = AF_INET6;
a.sin6_addr = in6addr_any;
a.sin6_port = htons(port);
if (bind(fd, (struct sockaddr *)&a, sizeof(a)) != 0) {
NAUT_ERROR("bind(:%u): %s", port, strerror(errno));
close(fd);
return -1;
}
if (listen(fd, backlog) != 0) {
NAUT_ERROR("listen: %s", strerror(errno));
close(fd);
return -1;
}
return fd;
}
void naut_net_tune_peer(int fd) {
setopt(fd, IPPROTO_TCP, TCP_NODELAY, 1);
setopt(fd, SOL_SOCKET, SO_SNDBUF, 4 * 1024 * 1024);
setopt(fd, SOL_SOCKET, SO_RCVBUF, 4 * 1024 * 1024);
#ifdef TCP_QUICKACK
setopt(fd, IPPROTO_TCP, TCP_QUICKACK, 1);
#endif
}
void naut_net_set_bufsizes(int fd, int sndbuf, int rcvbuf) {
if (sndbuf > 0) setopt(fd, SOL_SOCKET, SO_SNDBUF, sndbuf);
if (rcvbuf > 0) setopt(fd, SOL_SOCKET, SO_RCVBUF, rcvbuf);
}
int naut_net_set_nonblock(int fd, bool on) {
int fl = fcntl(fd, F_GETFL, 0);
if (fl < 0) return NAUT_ERR_IO;
fl = on ? (fl | O_NONBLOCK) : (fl & ~O_NONBLOCK);
return fcntl(fd, F_SETFL, fl) == 0 ? NAUT_OK : NAUT_ERR_IO;
}

19
src/platform/system.c Normal file
View file

@ -0,0 +1,19 @@
#include "naut/system.h"
#include <sched.h>
#include <unistd.h>
naut_err naut_pin_current_thread(int cpu) {
int online = naut_online_cpus();
if (cpu < 0 || cpu >= online) return NAUT_ERR_RANGE;
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET((unsigned)cpu, &set);
return sched_setaffinity(0, sizeof set, &set) == 0
? NAUT_OK : NAUT_ERR_IO;
}
int naut_online_cpus(void) {
long count = sysconf(_SC_NPROCESSORS_ONLN);
return count > 0 && count <= INT32_MAX ? (int)count : 1;
}

151
src/platform/uring.c Normal file
View file

@ -0,0 +1,151 @@
#include "naut/uring.h"
#include "naut/log.h"
#include <errno.h>
#include <string.h>
#include <sys/uio.h>
naut_err naut_ring_init(naut_ring *r, unsigned entries, bool sqpoll) {
return naut_ring_init_cpu(r, entries, sqpoll, -1);
}
/* Build the params for one setup attempt. COOP_TASKRUN runs completion task
* work in the submitter's context to cut IPIs when one thread owns the ring
* but it is *mutually exclusive* with SQPOLL (a kernel thread submits there, so
* there is no cooperative submitter context), and the kernel rejects the combo
* with -EINVAL. So pick exactly one of the two. */
static void ring_params(struct io_uring_params *p, bool sqpoll, int sqpoll_cpu) {
memset(p, 0, sizeof(*p));
p->flags = IORING_SETUP_SINGLE_ISSUER;
if (sqpoll) {
p->flags |= IORING_SETUP_SQPOLL;
p->sq_thread_idle = 1000; /* ms before the poll thread sleeps */
if (sqpoll_cpu >= 0) {
p->flags |= IORING_SETUP_SQ_AFF;
p->sq_thread_cpu = (unsigned)sqpoll_cpu;
}
} else {
p->flags |= IORING_SETUP_COOP_TASKRUN;
}
}
naut_err naut_ring_init_cpu(naut_ring *r, unsigned entries, bool sqpoll,
int sqpoll_cpu) {
struct io_uring_params p;
ring_params(&p, sqpoll, sqpoll_cpu);
int rc = io_uring_queue_init_params(entries, &r->ring, &p);
if (rc < 0 && sqpoll) {
NAUT_WARN("io_uring SQPOLL setup failed (%s), retrying without it",
strerror(-rc));
ring_params(&p, false, -1);
rc = io_uring_queue_init_params(entries, &r->ring, &p);
sqpoll = false;
}
if (rc < 0) {
/* Older fallbacks: SINGLE_ISSUER needs ~6.0; we have it, but be safe. */
rc = io_uring_queue_init(entries, &r->ring, 0);
if (rc < 0) {
NAUT_ERROR("io_uring_queue_init: %s", strerror(-rc));
return NAUT_ERR_NOSYS;
}
sqpoll = false;
}
r->sqpoll = sqpoll;
r->send_zc = false;
r->msg_ring = false;
r->buffers_registered = false;
r->recv_fixed = false;
NAUT_INFO("io_uring: %u entries%s", entries, sqpoll ? ", sqpoll" : "");
return NAUT_OK;
}
void naut_ring_close(naut_ring *r) {
naut_ring_unregister_buffers(r);
io_uring_queue_exit(&r->ring);
}
naut_err naut_ring_probe(naut_ring *r) {
struct io_uring_probe *p = io_uring_get_probe();
if (!p) { NAUT_WARN("io_uring_get_probe failed"); return NAUT_OK; }
struct { int op; const char *name; bool required; } want[] = {
{ IORING_OP_RECV, "recv", true },
{ IORING_OP_SEND, "send", true },
{ IORING_OP_ACCEPT, "accept", true },
{ IORING_OP_READ_FIXED, "read_fixed", true },
{ IORING_OP_WRITE_FIXED, "write_fixed", true },
{ IORING_OP_SEND_ZC, "send_zc", false }, /* seed fast-path */
{ IORING_OP_MSG_RING, "msg_ring", false }, /* cross-ring wake */
};
naut_err result = NAUT_OK;
for (size_t i = 0; i < NAUT_ARRAY_LEN(want); i++) {
bool ok = io_uring_opcode_supported(p, want[i].op);
if (want[i].op == IORING_OP_SEND_ZC) r->send_zc = ok;
if (want[i].op == IORING_OP_MSG_RING) r->msg_ring = ok;
if (!ok && want[i].required) {
NAUT_ERROR("io_uring missing required op: %s", want[i].name);
result = NAUT_ERR_NOSYS;
} else if (!ok) {
NAUT_WARN("io_uring optional op unavailable: %s (degraded)", want[i].name);
}
}
io_uring_free_probe(p);
NAUT_INFO("io_uring optional features: send_zc=%s, msg_ring=%s",
r->send_zc ? "yes" : "no",
r->msg_ring ? "yes" : "no");
return result;
}
naut_err naut_ring_register_bufpool(naut_ring *r, const naut_bufpool *pool) {
if (!r || !pool || r->buffers_registered) return NAUT_ERR_INVAL;
size_t bytes = 0;
void *slab = naut_bufpool_slab(pool, &bytes);
if (!slab || bytes == 0) return NAUT_ERR_INVAL;
struct iovec iov = { .iov_base = slab, .iov_len = bytes };
int rc = io_uring_register_buffers(&r->ring, &iov, 1);
if (rc < 0) {
NAUT_WARN("io_uring fixed-buffer registration failed: %s",
strerror(-rc));
return rc == -ENOMEM || rc == -EPERM ? NAUT_ERR_NOSYS : NAUT_ERR_IO;
}
r->buffers_registered = true;
r->recv_fixed = true;
NAUT_INFO("io_uring: registered %zu MiB buffer slab", bytes >> 20);
return NAUT_OK;
}
void naut_ring_unregister_buffers(naut_ring *r) {
if (!r || !r->buffers_registered) return;
int rc = io_uring_unregister_buffers(&r->ring);
if (rc < 0)
NAUT_WARN("io_uring unregister buffers: %s", strerror(-rc));
r->buffers_registered = false;
r->recv_fixed = false;
}
bool naut_ring_prep_send(naut_ring *r, struct io_uring_sqe *sqe, int fd,
const void *buf, size_t len, int flags,
bool prefer_zero_copy) {
if (prefer_zero_copy && r && r->send_zc) {
if (r->buffers_registered)
io_uring_prep_send_zc_fixed(sqe, fd, buf, len, flags, 0, 0);
else
io_uring_prep_send_zc(sqe, fd, buf, len, flags, 0);
sqe->ioprio |= IORING_SEND_ZC_REPORT_USAGE;
return true;
}
io_uring_prep_send(sqe, fd, buf, len, flags);
return false;
}
bool naut_ring_prep_recv(naut_ring *r, struct io_uring_sqe *sqe, int fd,
void *buf, size_t len, int flags) {
io_uring_prep_recv(sqe, fd, buf, len, flags);
if (r && r->buffers_registered && r->recv_fixed) {
sqe->ioprio |= IORING_RECVSEND_FIXED_BUF;
sqe->buf_index = 0;
return true;
}
return false;
}

354
src/plugin/plugin.c Normal file
View file

@ -0,0 +1,354 @@
#include "naut/plugin.h"
#include "naut/log.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
naut_plugin_rpc_fn callback;
void *context;
char *method;
} rpc_adapter;
typedef struct {
naut_plugin_event_fn callback;
void *context;
} event_adapter;
typedef struct {
void *handle;
char *path;
char *name;
uint64_t *subscriptions;
size_t subscription_count;
size_t subscription_capacity;
} loaded_plugin;
struct naut_plugin_manager {
naut_rpc_registry *rpc;
naut_event_bus *events;
loaded_plugin *plugins;
size_t plugin_count;
size_t plugin_capacity;
naut_storage_backend_v1 *storage;
size_t storage_count;
size_t storage_capacity;
rpc_adapter **rpc_adapters;
size_t rpc_count;
size_t rpc_capacity;
event_adapter **event_adapters;
size_t event_count;
size_t event_capacity;
loaded_plugin *loading;
};
static json_t *plugin_rpc_adapter(void *opaque, const json_t *params,
naut_err *error) {
rpc_adapter *adapter = opaque;
char *request =
json_dumps(params ? params : json_null(), JSON_COMPACT | JSON_ENCODE_ANY);
if (!request) {
*error = NAUT_ERR_NOMEM;
return NULL;
}
char *response = NULL;
*error = adapter->callback(adapter->context, request, &response);
free(request);
if (*error != NAUT_OK) {
free(response);
return NULL;
}
if (!response) return json_null();
json_error_t json_error;
json_t *json = json_loads(response, JSON_REJECT_DUPLICATES, &json_error);
free(response);
if (!json) {
*error = NAUT_ERR_PROTO;
return NULL;
}
return json;
}
static void plugin_event_adapter(void *opaque, const naut_event *event) {
event_adapter *adapter = opaque;
adapter->callback(adapter->context, event);
}
static naut_err host_set_name(void *opaque, const char *name) {
naut_plugin_manager *manager = opaque;
if (!manager->loading || !name || !*name || manager->loading->name)
return NAUT_ERR_INVAL;
manager->loading->name = strdup(name);
return manager->loading->name ? NAUT_OK : NAUT_ERR_NOMEM;
}
static naut_err reserve_pointer(void ***items, size_t *count, size_t *capacity,
void *item) {
if (*count == *capacity) {
size_t next_capacity = *capacity ? *capacity * 2 : 8;
void **next = realloc(*items, next_capacity * sizeof(*next));
if (!next) return NAUT_ERR_NOMEM;
*items = next;
*capacity = next_capacity;
}
(*items)[(*count)++] = item;
return NAUT_OK;
}
static naut_err host_register_rpc(void *opaque, const char *method,
naut_plugin_rpc_fn callback, void *context) {
naut_plugin_manager *manager = opaque;
if (!manager->loading || !method || !*method || !callback)
return NAUT_ERR_INVAL;
rpc_adapter *adapter = malloc(sizeof(*adapter));
if (!adapter) return NAUT_ERR_NOMEM;
adapter->callback = callback;
adapter->context = context;
adapter->method = strdup(method);
if (!adapter->method) {
free(adapter);
return NAUT_ERR_NOMEM;
}
naut_err error = naut_rpc_register(manager->rpc, method,
plugin_rpc_adapter, adapter);
if (error != NAUT_OK) {
free(adapter->method);
free(adapter);
return error;
}
error = reserve_pointer((void ***)&manager->rpc_adapters,
&manager->rpc_count, &manager->rpc_capacity,
adapter);
if (error != NAUT_OK) {
naut_rpc_unregister(manager->rpc, method);
free(adapter->method);
free(adapter);
}
return error;
}
static naut_err host_register_storage(
void *opaque, const naut_storage_backend_v1 *backend) {
naut_plugin_manager *manager = opaque;
if (!manager->loading || !backend ||
backend->abi_version != NAUT_PLUGIN_ABI_VERSION ||
backend->struct_size < sizeof(*backend) ||
!backend->name || !backend->open || !backend->close ||
!backend->read || !backend->write)
return NAUT_ERR_INVAL;
for (size_t i = 0; i < manager->storage_count; i++)
if (strcmp(manager->storage[i].name, backend->name) == 0)
return NAUT_ERR_INVAL;
if (manager->storage_count == manager->storage_capacity) {
size_t capacity =
manager->storage_capacity ? manager->storage_capacity * 2 : 8;
naut_storage_backend_v1 *next =
realloc(manager->storage, capacity * sizeof(*next));
if (!next) return NAUT_ERR_NOMEM;
manager->storage = next;
manager->storage_capacity = capacity;
}
manager->storage[manager->storage_count++] = *backend;
return NAUT_OK;
}
static naut_err host_subscribe_event(void *opaque,
naut_plugin_event_fn callback,
void *context) {
naut_plugin_manager *manager = opaque;
if (!manager->loading || !callback) return NAUT_ERR_INVAL;
event_adapter *adapter = malloc(sizeof(*adapter));
if (!adapter) return NAUT_ERR_NOMEM;
adapter->callback = callback;
adapter->context = context;
uint64_t id = 0;
naut_err error = naut_event_subscribe(manager->events,
plugin_event_adapter,
adapter, &id);
if (error != NAUT_OK) {
free(adapter);
return error;
}
loaded_plugin *plugin = manager->loading;
if (plugin->subscription_count == plugin->subscription_capacity) {
size_t capacity = plugin->subscription_capacity
? plugin->subscription_capacity * 2 : 4;
uint64_t *next =
realloc(plugin->subscriptions, capacity * sizeof(*next));
if (!next) {
naut_event_unsubscribe(manager->events, id);
free(adapter);
return NAUT_ERR_NOMEM;
}
plugin->subscriptions = next;
plugin->subscription_capacity = capacity;
}
plugin->subscriptions[plugin->subscription_count++] = id;
error = reserve_pointer((void ***)&manager->event_adapters,
&manager->event_count,
&manager->event_capacity, adapter);
if (error != NAUT_OK) {
plugin->subscription_count--;
naut_event_unsubscribe(manager->events, id);
free(adapter);
}
return error;
}
static void host_emit_event(void *opaque, const naut_event *event) {
naut_plugin_manager *manager = opaque;
naut_event_emit(manager->events, event);
}
static void host_log(void *opaque, int level, const char *message) {
(void)opaque;
if (!message) return;
if (level <= 0) NAUT_ERROR("plugin: %s", message);
else if (level == 1) NAUT_WARN("plugin: %s", message);
else NAUT_INFO("plugin: %s", message);
}
naut_plugin_manager *naut_plugin_manager_create(
naut_rpc_registry *rpc, naut_event_bus *events) {
if (!rpc || !events) return NULL;
naut_plugin_manager *manager = calloc(1, sizeof(*manager));
if (!manager) return NULL;
manager->rpc = rpc;
manager->events = events;
return manager;
}
void naut_plugin_manager_destroy(naut_plugin_manager *manager) {
if (!manager) return;
for (size_t i = 0; i < manager->rpc_count; i++)
naut_rpc_unregister(manager->rpc, manager->rpc_adapters[i]->method);
for (size_t i = 0; i < manager->plugin_count; i++) {
loaded_plugin *plugin = &manager->plugins[i];
for (size_t s = 0; s < plugin->subscription_count; s++)
naut_event_unsubscribe(manager->events, plugin->subscriptions[s]);
free(plugin->subscriptions);
free(plugin->name);
free(plugin->path);
if (plugin->handle) dlclose(plugin->handle);
}
for (size_t i = 0; i < manager->rpc_count; i++) {
free(manager->rpc_adapters[i]->method);
free(manager->rpc_adapters[i]);
}
for (size_t i = 0; i < manager->event_count; i++)
free(manager->event_adapters[i]);
free(manager->rpc_adapters);
free(manager->event_adapters);
free(manager->plugins);
free(manager->storage);
free(manager);
}
naut_err naut_plugin_load(naut_plugin_manager *manager, const char *path) {
if (!manager || !path || !*path || manager->loading)
return NAUT_ERR_INVAL;
if (manager->plugin_count == manager->plugin_capacity) {
size_t capacity =
manager->plugin_capacity ? manager->plugin_capacity * 2 : 4;
loaded_plugin *next =
realloc(manager->plugins, capacity * sizeof(*next));
if (!next) return NAUT_ERR_NOMEM;
manager->plugins = next;
manager->plugin_capacity = capacity;
}
loaded_plugin *plugin = &manager->plugins[manager->plugin_count];
memset(plugin, 0, sizeof(*plugin));
plugin->path = strdup(path);
plugin->handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
if (!plugin->path || !plugin->handle) {
NAUT_ERROR("plugin load %s: %s", path, dlerror());
free(plugin->path);
memset(plugin, 0, sizeof(*plugin));
return NAUT_ERR_IO;
}
dlerror();
naut_plugin_register_fn register_plugin =
(naut_plugin_register_fn)dlsym(plugin->handle,
"naut_plugin_register");
const char *symbol_error = dlerror();
if (symbol_error || !register_plugin) {
NAUT_ERROR("plugin %s has no naut_plugin_register: %s",
path, symbol_error ? symbol_error : "missing");
dlclose(plugin->handle);
free(plugin->path);
memset(plugin, 0, sizeof(*plugin));
return NAUT_ERR_PROTO;
}
naut_host_api host = {
.abi_version = NAUT_PLUGIN_ABI_VERSION,
.struct_size = sizeof(host),
.host_context = manager,
.set_plugin_name = host_set_name,
.register_rpc = host_register_rpc,
.register_storage_backend = host_register_storage,
.subscribe_event = host_subscribe_event,
.emit_event = host_emit_event,
.log = host_log,
};
size_t rpc_start = manager->rpc_count;
size_t event_start = manager->event_count;
size_t storage_start = manager->storage_count;
manager->loading = plugin;
naut_err error = register_plugin(&host);
manager->loading = NULL;
if (error != NAUT_OK || !plugin->name) {
NAUT_ERROR("plugin registration failed: %s", path);
for (size_t i = 0; i < plugin->subscription_count; i++)
naut_event_unsubscribe(manager->events,
plugin->subscriptions[i]);
for (size_t i = event_start; i < manager->event_count; i++)
free(manager->event_adapters[i]);
manager->event_count = event_start;
for (size_t i = rpc_start; i < manager->rpc_count; i++) {
naut_rpc_unregister(manager->rpc,
manager->rpc_adapters[i]->method);
free(manager->rpc_adapters[i]->method);
free(manager->rpc_adapters[i]);
}
manager->rpc_count = rpc_start;
manager->storage_count = storage_start;
dlclose(plugin->handle);
free(plugin->path);
free(plugin->name);
free(plugin->subscriptions);
memset(plugin, 0, sizeof(*plugin));
return error != NAUT_OK ? error : NAUT_ERR_PROTO;
}
manager->plugin_count++;
NAUT_INFO("loaded plugin '%s' from %s", plugin->name, path);
return NAUT_OK;
}
size_t naut_plugin_count(const naut_plugin_manager *manager) {
return manager ? manager->plugin_count : 0;
}
const char *naut_plugin_name(const naut_plugin_manager *manager, size_t index) {
return manager && index < manager->plugin_count
? manager->plugins[index].name : NULL;
}
size_t naut_plugin_storage_count(const naut_plugin_manager *manager) {
return manager ? manager->storage_count : 0;
}
const char *naut_plugin_storage_name(const naut_plugin_manager *manager,
size_t index) {
return manager && index < manager->storage_count
? manager->storage[index].name : NULL;
}
const naut_storage_backend_v1 *naut_plugin_storage_backend(
const naut_plugin_manager *manager, const char *name) {
if (!manager || !name) return NULL;
for (size_t i = 0; i < manager->storage_count; i++)
if (strcmp(manager->storage[i].name, name) == 0)
return &manager->storage[i];
return NULL;
}

261
src/rpc/rpc.c Normal file
View file

@ -0,0 +1,261 @@
#include "naut/rpc.h"
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define RPC_MAGIC 0x4e545250u
typedef struct {
char *method;
naut_rpc_handler handler;
void *context;
} command;
struct naut_rpc_registry {
pthread_mutex_t lock;
command *commands;
size_t count;
size_t capacity;
};
typedef struct NAUT_PACKED {
uint32_t magic;
uint16_t version;
uint16_t type;
uint32_t length;
} frame_header;
static bool write_all(int fd, const void *data, size_t length) {
const uint8_t *p = data;
while (length) {
ssize_t n = write(fd, p, length);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
length -= (size_t)n;
}
return true;
}
static bool read_all(int fd, void *data, size_t length) {
uint8_t *p = data;
while (length) {
ssize_t n = recv(fd, p, length, 0);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
if (n == 0) return false;
p += n;
length -= (size_t)n;
}
return true;
}
naut_rpc_registry *naut_rpc_registry_create(void) {
naut_rpc_registry *registry = calloc(1, sizeof(*registry));
if (!registry) return NULL;
if (pthread_mutex_init(&registry->lock, NULL) != 0) {
free(registry);
return NULL;
}
return registry;
}
void naut_rpc_registry_destroy(naut_rpc_registry *registry) {
if (!registry) return;
for (size_t i = 0; i < registry->count; i++)
free(registry->commands[i].method);
free(registry->commands);
pthread_mutex_destroy(&registry->lock);
free(registry);
}
naut_err naut_rpc_register(naut_rpc_registry *registry, const char *method,
naut_rpc_handler handler, void *context) {
if (!registry || !method || !*method || !handler) return NAUT_ERR_INVAL;
pthread_mutex_lock(&registry->lock);
for (size_t i = 0; i < registry->count; i++) {
if (strcmp(registry->commands[i].method, method) == 0) {
pthread_mutex_unlock(&registry->lock);
return NAUT_ERR_INVAL;
}
}
if (registry->count == registry->capacity) {
size_t capacity = registry->capacity ? registry->capacity * 2 : 16;
command *next = realloc(registry->commands,
capacity * sizeof(*next));
if (!next) {
pthread_mutex_unlock(&registry->lock);
return NAUT_ERR_NOMEM;
}
registry->commands = next;
registry->capacity = capacity;
}
char *copy = strdup(method);
if (!copy) {
pthread_mutex_unlock(&registry->lock);
return NAUT_ERR_NOMEM;
}
registry->commands[registry->count++] = (command) {
.method = copy,
.handler = handler,
.context = context,
};
pthread_mutex_unlock(&registry->lock);
return NAUT_OK;
}
void naut_rpc_unregister(naut_rpc_registry *registry, const char *method) {
if (!registry || !method) return;
pthread_mutex_lock(&registry->lock);
for (size_t i = 0; i < registry->count; i++) {
if (strcmp(registry->commands[i].method, method) != 0) continue;
free(registry->commands[i].method);
registry->commands[i] = registry->commands[--registry->count];
break;
}
pthread_mutex_unlock(&registry->lock);
}
json_t *naut_rpc_dispatch(naut_rpc_registry *registry, const char *method,
const json_t *params, naut_err *error) {
if (error) *error = NAUT_ERR_INVAL;
if (!registry || !method) return NULL;
pthread_mutex_lock(&registry->lock);
naut_rpc_handler handler = NULL;
void *context = NULL;
for (size_t i = 0; i < registry->count; i++) {
if (strcmp(registry->commands[i].method, method) == 0) {
handler = registry->commands[i].handler;
context = registry->commands[i].context;
break;
}
}
pthread_mutex_unlock(&registry->lock);
if (!handler) return NULL;
if (error) *error = NAUT_OK;
return handler(context, params, error);
}
naut_err naut_rpc_send_json(int fd, naut_rpc_frame_type type,
const json_t *payload) {
if (fd < 0 || !payload) return NAUT_ERR_INVAL;
char *text =
json_dumps(payload, JSON_COMPACT | JSON_SORT_KEYS | JSON_ENCODE_ANY);
if (!text) return NAUT_ERR_NOMEM;
size_t length = strlen(text);
if (length > NAUT_RPC_MAX_PAYLOAD) {
free(text);
return NAUT_ERR_RANGE;
}
frame_header header = {
.magic = htonl(RPC_MAGIC),
.version = htons(NAUT_RPC_VERSION),
.type = htons((uint16_t)type),
.length = htonl((uint32_t)length),
};
bool ok = write_all(fd, &header, sizeof header) &&
write_all(fd, text, length);
free(text);
return ok ? NAUT_OK : NAUT_ERR_IO;
}
naut_err naut_rpc_recv_json(int fd, naut_rpc_frame_type *type,
json_t **payload) {
if (fd < 0 || !type || !payload) return NAUT_ERR_INVAL;
*payload = NULL;
frame_header header;
if (!read_all(fd, &header, sizeof header)) return NAUT_ERR_IO;
if (ntohl(header.magic) != RPC_MAGIC ||
ntohs(header.version) != NAUT_RPC_VERSION)
return NAUT_ERR_PROTO;
uint16_t raw_type = ntohs(header.type);
uint32_t length = ntohl(header.length);
if (raw_type < NAUT_RPC_REQUEST || raw_type > NAUT_RPC_EVENT ||
length > NAUT_RPC_MAX_PAYLOAD)
return NAUT_ERR_PROTO;
char *text = malloc((size_t)length + 1);
if (!text) return NAUT_ERR_NOMEM;
if (!read_all(fd, text, length)) {
free(text);
return NAUT_ERR_IO;
}
text[length] = 0;
json_error_t json_error;
json_t *json = json_loadb(text, length, JSON_REJECT_DUPLICATES,
&json_error);
free(text);
if (!json) return NAUT_ERR_PROTO;
*type = (naut_rpc_frame_type)raw_type;
*payload = json;
return NAUT_OK;
}
int naut_rpc_connect_unix(const char *path) {
if (!path || !*path) return -1;
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un address;
memset(&address, 0, sizeof address);
address.sun_family = AF_UNIX;
if (strlen(path) >= sizeof address.sun_path) {
close(fd);
return -1;
}
strcpy(address.sun_path, path);
if (connect(fd, (struct sockaddr *)&address, sizeof address) != 0) {
close(fd);
return -1;
}
return fd;
}
naut_err naut_rpc_call(const char *socket_path, const char *method,
const json_t *params, json_t **response) {
if (!socket_path || !method || !response) return NAUT_ERR_INVAL;
*response = NULL;
int fd = naut_rpc_connect_unix(socket_path);
if (fd < 0) return NAUT_ERR_IO;
json_t *request = json_object();
json_object_set_new(request, "method", json_string(method));
json_object_set(request, "params",
params ? (json_t *)params : json_null());
naut_err error = naut_rpc_send_json(fd, NAUT_RPC_REQUEST, request);
json_decref(request);
if (error == NAUT_OK) {
naut_rpc_frame_type type;
error = naut_rpc_recv_json(fd, &type, response);
if (error == NAUT_OK && type != NAUT_RPC_RESPONSE) {
json_decref(*response);
*response = NULL;
error = NAUT_ERR_PROTO;
}
}
close(fd);
return error;
}
json_t *naut_rpc_event_json(const naut_event *event) {
if (!event) return NULL;
json_t *json = json_object();
json_object_set_new(json, "event",
json_string(naut_event_type_name(event->type)));
json_object_set_new(json, "torrent_id",
json_integer((json_int_t)event->torrent_id));
json_object_set_new(json, "index", json_integer(event->index));
if (event->message)
json_object_set_new(json, "message", json_string(event->message));
if (event->path)
json_object_set_new(json, "path", json_string(event->path));
return json;
}

312
src/script/script.c Normal file
View file

@ -0,0 +1,312 @@
#include "naut/script.h"
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <limits.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
naut_event event;
char message[256];
char path[PATH_MAX];
} script_job;
struct naut_script {
naut_event_bus *events;
uint64_t subscription;
lua_State *lua;
pthread_t thread;
pthread_mutex_t lock;
pthread_cond_t ready;
script_job *queue;
size_t capacity;
size_t head;
size_t count;
bool stopping;
naut_script_move_file_cb move_file;
void *move_context;
_Atomic uint64_t queued;
_Atomic uint64_t handled;
_Atomic uint64_t dropped;
_Atomic uint64_t errors;
_Atomic uint64_t move_requests;
char last_error[256];
};
static const char *hook_names[] = {
[NAUT_EVENT_TORRENT_ADDED] = "on_torrent_added",
[NAUT_EVENT_PIECE_COMPLETE] = "on_piece_complete",
[NAUT_EVENT_FILE_COMPLETE] = "on_file_complete",
[NAUT_EVENT_TORRENT_FINISHED] = "on_torrent_finished",
[NAUT_EVENT_PEER_CONNECTED] = "on_peer_connected",
[NAUT_EVENT_ALERT] = "on_alert",
};
static void set_last_error(naut_script *script, const char *message) {
pthread_mutex_lock(&script->lock);
snprintf(script->last_error, sizeof script->last_error, "%s",
message ? message : "unknown Lua error");
pthread_mutex_unlock(&script->lock);
}
static naut_script *lua_script(lua_State *lua) {
return lua_touserdata(lua, lua_upvalueindex(1));
}
static int lua_move_file(lua_State *lua) {
naut_script *script = lua_script(lua);
lua_Integer torrent_id = luaL_checkinteger(lua, 1);
lua_Integer file_index = luaL_checkinteger(lua, 2);
const char *destination = luaL_checkstring(lua, 3);
if (torrent_id < 0 || file_index < 0 ||
(uint64_t)file_index > UINT32_MAX)
return luaL_error(lua, "move_file arguments out of range");
if (!script->move_file)
return luaL_error(lua, "move_file is unavailable");
naut_err error = script->move_file(script->move_context,
(uint64_t)torrent_id,
(uint32_t)file_index,
destination);
if (error != NAUT_OK)
return luaL_error(lua, "move_file failed: %d", error);
atomic_fetch_add_explicit(&script->move_requests, 1,
memory_order_relaxed);
return 0;
}
static void sandbox(lua_State *lua) {
/* Remove every documented route to the filesystem, subprocesses, native
* module loading, and raw chunk compilation. `load`/`loadstring` are
* blocked too: with the default "bt" mode they accept *binary* chunks, and
* a crafted bytecode chunk can escape the VM entirely so even though
* scripts are operator-supplied, we deny the bytecode-loader as
* defense-in-depth. */
static const char *blocked[] = {
"debug", "dofile", "io", "load", "loadfile", "loadstring",
"os", "package", "require",
};
for (size_t i = 0; i < NAUT_ARRAY_LEN(blocked); i++) {
lua_pushnil(lua);
lua_setglobal(lua, blocked[i]);
}
}
static void install_api(naut_script *script) {
lua_State *lua = script->lua;
lua_newtable(lua);
lua_pushlightuserdata(lua, script);
lua_pushcclosure(lua, lua_move_file, 1);
lua_setfield(lua, -2, "move_file");
lua_setglobal(lua, "naut");
}
static void push_event(lua_State *lua, const naut_event *event) {
lua_createtable(lua, 0, 5);
lua_pushstring(lua, naut_event_type_name(event->type));
lua_setfield(lua, -2, "type");
lua_pushinteger(lua, (lua_Integer)event->torrent_id);
lua_setfield(lua, -2, "torrent_id");
lua_pushinteger(lua, (lua_Integer)event->index);
lua_setfield(lua, -2, "index");
if (event->message) {
lua_pushstring(lua, event->message);
lua_setfield(lua, -2, "message");
}
if (event->path) {
lua_pushstring(lua, event->path);
lua_setfield(lua, -2, "path");
}
}
static void run_hook(naut_script *script, const naut_event *event) {
if ((size_t)event->type >= NAUT_ARRAY_LEN(hook_names)) {
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, "unknown event type");
return;
}
const char *hook = hook_names[event->type];
lua_getglobal(script->lua, hook);
if (lua_isnil(script->lua, -1)) {
lua_pop(script->lua, 1);
return;
}
if (!lua_isfunction(script->lua, -1)) {
lua_pop(script->lua, 1);
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, "event hook is not a function");
return;
}
push_event(script->lua, event);
if (lua_pcall(script->lua, 1, 0, 0) != LUA_OK) {
atomic_fetch_add_explicit(&script->errors, 1, memory_order_relaxed);
set_last_error(script, lua_tostring(script->lua, -1));
lua_pop(script->lua, 1);
return;
}
atomic_fetch_add_explicit(&script->handled, 1, memory_order_relaxed);
}
static bool pop_job(naut_script *script, script_job *job) {
pthread_mutex_lock(&script->lock);
while (!script->stopping && script->count == 0)
pthread_cond_wait(&script->ready, &script->lock);
if (script->count == 0) {
pthread_mutex_unlock(&script->lock);
return false;
}
*job = script->queue[script->head];
if (job->event.message) job->event.message = job->message;
if (job->event.path) job->event.path = job->path;
script->head = (script->head + 1) % script->capacity;
script->count--;
pthread_mutex_unlock(&script->lock);
return true;
}
static void *script_worker(void *opaque) {
naut_script *script = opaque;
script_job job;
while (pop_job(script, &job))
run_hook(script, &job.event);
return NULL;
}
static void queue_event(void *opaque, const naut_event *event) {
naut_script *script = opaque;
pthread_mutex_lock(&script->lock);
if (script->stopping || script->count == script->capacity) {
pthread_mutex_unlock(&script->lock);
atomic_fetch_add_explicit(&script->dropped, 1, memory_order_relaxed);
return;
}
size_t tail = (script->head + script->count) % script->capacity;
script_job *job = &script->queue[tail];
memset(job, 0, sizeof(*job));
job->event = *event;
if (event->message) {
snprintf(job->message, sizeof job->message, "%s", event->message);
job->event.message = job->message;
}
if (event->path) {
snprintf(job->path, sizeof job->path, "%s", event->path);
job->event.path = job->path;
}
script->count++;
pthread_cond_signal(&script->ready);
pthread_mutex_unlock(&script->lock);
atomic_fetch_add_explicit(&script->queued, 1, memory_order_relaxed);
}
naut_script *naut_script_create(naut_event_bus *events,
const char *script_path,
size_t queue_capacity,
naut_script_move_file_cb move_file,
void *move_context,
naut_err *error) {
if (error) *error = NAUT_ERR_INVAL;
if (!events || !script_path || !*script_path || queue_capacity == 0)
return NULL;
naut_script *script = calloc(1, sizeof(*script));
if (!script) {
if (error) *error = NAUT_ERR_NOMEM;
return NULL;
}
script->events = events;
script->capacity = queue_capacity;
script->move_file = move_file;
script->move_context = move_context;
script->queue = calloc(queue_capacity, sizeof(*script->queue));
if (!script->queue) {
if (error) *error = NAUT_ERR_NOMEM;
free(script);
return NULL;
}
if (pthread_mutex_init(&script->lock, NULL) != 0) {
if (error) *error = NAUT_ERR_NOMEM;
free(script->queue);
free(script);
return NULL;
}
if (pthread_cond_init(&script->ready, NULL) != 0) {
if (error) *error = NAUT_ERR_NOMEM;
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
return NULL;
}
script->lua = luaL_newstate();
if (!script->lua) goto fail;
luaL_openlibs(script->lua);
sandbox(script->lua);
install_api(script);
if (luaL_loadfile(script->lua, script_path) != LUA_OK ||
lua_pcall(script->lua, 0, 0, 0) != LUA_OK) {
set_last_error(script, lua_tostring(script->lua, -1));
if (error) *error = NAUT_ERR_PROTO;
goto fail;
}
if (naut_event_subscribe(events, queue_event, script,
&script->subscription) != NAUT_OK)
goto fail;
if (pthread_create(&script->thread, NULL, script_worker, script) != 0) {
naut_event_unsubscribe(events, script->subscription);
script->subscription = 0;
goto fail;
}
if (error) *error = NAUT_OK;
return script;
fail:
if (error && *error == NAUT_ERR_INVAL) *error = NAUT_ERR_NOMEM;
if (script->lua) lua_close(script->lua);
pthread_cond_destroy(&script->ready);
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
return NULL;
}
void naut_script_destroy(naut_script *script) {
if (!script) return;
naut_event_unsubscribe(script->events, script->subscription);
pthread_mutex_lock(&script->lock);
script->stopping = true;
pthread_cond_broadcast(&script->ready);
pthread_mutex_unlock(&script->lock);
pthread_join(script->thread, NULL);
lua_close(script->lua);
pthread_cond_destroy(&script->ready);
pthread_mutex_destroy(&script->lock);
free(script->queue);
free(script);
}
void naut_script_get_stats(const naut_script *script,
naut_script_stats *stats) {
if (!script || !stats) return;
*stats = (naut_script_stats) {
.queued = atomic_load_explicit(&script->queued, memory_order_relaxed),
.handled = atomic_load_explicit(&script->handled,
memory_order_relaxed),
.dropped = atomic_load_explicit(&script->dropped,
memory_order_relaxed),
.errors = atomic_load_explicit(&script->errors, memory_order_relaxed),
.move_requests = atomic_load_explicit(&script->move_requests,
memory_order_relaxed),
};
}
const char *naut_script_last_error(naut_script *script) {
if (!script) return "";
pthread_mutex_lock(&script->lock);
static _Thread_local char copy[256];
snprintf(copy, sizeof copy, "%s", script->last_error);
pthread_mutex_unlock(&script->lock);
return copy;
}

111
src/session/event.c Normal file
View file

@ -0,0 +1,111 @@
#include "naut/event.h"
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
uint64_t id;
naut_event_cb callback;
void *context;
} subscriber;
struct naut_event_bus {
pthread_mutex_t lock;
subscriber *subscribers;
size_t count;
size_t capacity;
uint64_t next_id;
};
naut_event_bus *naut_event_bus_create(void) {
naut_event_bus *bus = calloc(1, sizeof(*bus));
if (!bus) return NULL;
if (pthread_mutex_init(&bus->lock, NULL) != 0) {
free(bus);
return NULL;
}
bus->next_id = 1;
return bus;
}
void naut_event_bus_destroy(naut_event_bus *bus) {
if (!bus) return;
pthread_mutex_destroy(&bus->lock);
free(bus->subscribers);
free(bus);
}
naut_err naut_event_subscribe(naut_event_bus *bus, naut_event_cb callback,
void *context, uint64_t *subscription_id) {
if (!bus || !callback) return NAUT_ERR_INVAL;
pthread_mutex_lock(&bus->lock);
if (bus->count == bus->capacity) {
size_t capacity = bus->capacity ? bus->capacity * 2 : 8;
subscriber *next =
realloc(bus->subscribers, capacity * sizeof(*next));
if (!next) {
pthread_mutex_unlock(&bus->lock);
return NAUT_ERR_NOMEM;
}
bus->subscribers = next;
bus->capacity = capacity;
}
uint64_t id = bus->next_id++;
bus->subscribers[bus->count++] = (subscriber) {
.id = id,
.callback = callback,
.context = context,
};
pthread_mutex_unlock(&bus->lock);
if (subscription_id) *subscription_id = id;
return NAUT_OK;
}
void naut_event_unsubscribe(naut_event_bus *bus, uint64_t subscription_id) {
if (!bus || subscription_id == 0) return;
pthread_mutex_lock(&bus->lock);
for (size_t i = 0; i < bus->count; i++) {
if (bus->subscribers[i].id != subscription_id) continue;
bus->subscribers[i] = bus->subscribers[--bus->count];
break;
}
pthread_mutex_unlock(&bus->lock);
}
void naut_event_emit(naut_event_bus *bus, const naut_event *event) {
if (!bus || !event) return;
pthread_mutex_lock(&bus->lock);
size_t count = bus->count;
subscriber *snapshot =
count ? malloc(count * sizeof(*snapshot)) : NULL;
if (snapshot) memcpy(snapshot, bus->subscribers, count * sizeof(*snapshot));
pthread_mutex_unlock(&bus->lock);
if (count && !snapshot) return;
for (size_t i = 0; i < count; i++)
snapshot[i].callback(snapshot[i].context, event);
free(snapshot);
}
const char *naut_event_type_name(naut_event_type type) {
static const char *names[] = {
"torrent_added",
"piece_complete",
"file_complete",
"torrent_finished",
"peer_connected",
"alert",
};
return type < NAUT_ARRAY_LEN(names) ? names[type] : "unknown";
}
bool naut_event_type_parse(const char *name, naut_event_type *type) {
if (!name || !type) return false;
for (int i = NAUT_EVENT_TORRENT_ADDED; i <= NAUT_EVENT_ALERT; i++) {
if (strcmp(name, naut_event_type_name((naut_event_type)i)) == 0) {
*type = (naut_event_type)i;
return true;
}
}
return false;
}

71
src/session/session.c Normal file
View file

@ -0,0 +1,71 @@
#include "naut/session.h"
#include <stdlib.h>
typedef struct {
uint64_t id;
naut_storage *storage;
} entry;
struct naut_session {
entry *entries;
size_t count;
size_t capacity;
};
naut_session *naut_session_create(void) {
return calloc(1, sizeof(naut_session));
}
void naut_session_destroy(naut_session *s) {
if (!s) return;
for (size_t i = 0; i < s->count; i++)
naut_storage_close(s->entries[i].storage);
free(s->entries);
free(s);
}
static entry *find(const naut_session *s, uint64_t id) {
for (size_t i = 0; i < s->count; i++)
if (s->entries[i].id == id) return &s->entries[i];
return NULL;
}
naut_err naut_session_add(naut_session *s, uint64_t id, naut_storage *storage) {
if (!s || !storage) return NAUT_ERR_INVAL;
if (find(s, id)) return NAUT_ERR_INVAL;
if (s->count == s->capacity) {
size_t capacity = s->capacity ? s->capacity * 2 : 8;
entry *next = realloc(s->entries, capacity * sizeof(*next));
if (!next) return NAUT_ERR_NOMEM;
s->entries = next;
s->capacity = capacity;
}
s->entries[s->count++] = (entry){ .id = id, .storage = storage };
return NAUT_OK;
}
naut_err naut_session_remove(naut_session *s, uint64_t id) {
if (!s) return NAUT_ERR_INVAL;
entry *e = find(s, id);
if (!e) return NAUT_ERR_NOTFOUND;
naut_storage_close(e->storage);
*e = s->entries[--s->count];
return NAUT_OK;
}
bool naut_session_has(const naut_session *s, uint64_t id) {
return s && find(s, id);
}
size_t naut_session_count(const naut_session *s) {
return s ? s->count : 0;
}
naut_err naut_session_move_file(naut_session *s, uint64_t id,
uint32_t file_index, const char *dest) {
if (!s || !dest) return NAUT_ERR_INVAL;
entry *e = find(s, id);
if (!e) return NAUT_ERR_NOTFOUND;
return naut_storage_relocate(e->storage, file_index, dest);
}

237
src/storage/storage.c Normal file
View file

@ -0,0 +1,237 @@
#include "naut/storage.h"
#include "naut/log.h"
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
typedef struct {
int fd;
int direct_fd;
int64_t start; /* global offset of this file's first byte */
int64_t length;
char *path; /* full on-disk path (for relocate) */
bool externalized; /* moved out; region no longer backed here */
} file_slot;
struct naut_storage {
file_slot *files;
size_t nfiles;
int64_t total;
bool direct_enabled;
};
/* mkdir -p for the directory portion of `path` (path includes the filename). */
static naut_err make_parents(char *path) {
for (char *p = strchr(path + 1, '/'); p; p = strchr(p + 1, '/')) {
*p = 0;
if (mkdir(path, 0777) != 0 && errno != EEXIST) { *p = '/'; return NAUT_ERR_IO; }
*p = '/';
}
return NAUT_OK;
}
naut_storage *naut_storage_open(const naut_file *files, size_t nfiles,
const char *root, naut_err *err) {
const naut_storage_opts opts = {
.direct_io = false,
.preallocate = true,
};
return naut_storage_open_opts(files, nfiles, root, &opts, err);
}
naut_storage *naut_storage_open_opts(const naut_file *files, size_t nfiles,
const char *root,
const naut_storage_opts *opts,
naut_err *err) {
if (!files || nfiles == 0 || !root || !opts) {
if (err) *err = NAUT_ERR_INVAL;
return NULL;
}
naut_storage *s = calloc(1, sizeof(*s));
if (!s) { if (err) *err = NAUT_ERR_NOMEM; return NULL; }
s->files = calloc(nfiles, sizeof(file_slot));
if (!s->files) { free(s); if (err) *err = NAUT_ERR_NOMEM; return NULL; }
int64_t off = 0;
for (size_t i = 0; i < nfiles; i++) {
s->files[i].fd = -1;
s->files[i].direct_fd = -1;
char path[4096];
int n = snprintf(path, sizeof path, "%s/%s", root, files[i].path);
if (n < 0 || n >= (int)sizeof path) goto fail_io;
if (make_parents(path) != NAUT_OK) goto fail_io;
int fd = open(path, O_RDWR | O_CREAT, 0666);
if (fd < 0) { NAUT_ERROR("open %s: %s", path, strerror(errno)); goto fail_io; }
if (ftruncate(fd, files[i].length) != 0) {
NAUT_ERROR("ftruncate %s: %s", path, strerror(errno));
close(fd); goto fail_io;
}
if (opts->preallocate && files[i].length > 0) {
int rc = posix_fallocate(fd, 0, files[i].length);
if (rc != 0 && rc != EOPNOTSUPP && rc != ENOSYS)
NAUT_WARN("preallocate %s: %s", path, strerror(rc));
}
s->files[i].fd = fd;
#ifdef O_DIRECT
if (opts->direct_io && files[i].length > 0) {
int direct_fd = open(path, O_RDWR | O_DIRECT);
if (direct_fd >= 0) {
s->files[i].direct_fd = direct_fd;
s->direct_enabled = true;
} else {
NAUT_WARN("O_DIRECT unavailable for %s: %s", path,
strerror(errno));
}
}
#endif
s->files[i].start = off;
s->files[i].length = files[i].length;
s->files[i].path = strdup(path);
off += files[i].length;
s->nfiles++;
}
s->total = off;
if (opts->direct_io)
NAUT_INFO("storage: O_DIRECT %s with buffered edge fallback",
s->direct_enabled ? "enabled" : "unavailable");
if (err) *err = NAUT_OK;
return s;
fail_io:
naut_storage_close(s);
if (err) *err = NAUT_ERR_IO;
return NULL;
}
void naut_storage_close(naut_storage *s) {
if (!s) return;
for (size_t i = 0; i < s->nfiles; i++) {
if (s->files[i].direct_fd >= 0) close(s->files[i].direct_fd);
if (s->files[i].fd >= 0) close(s->files[i].fd);
free(s->files[i].path);
}
free(s->files);
free(s);
}
/* binary search for the file containing global offset */
static const file_slot *locate(const naut_storage *s, int64_t off) {
size_t lo = 0, hi = s->nfiles;
while (lo < hi) {
size_t mid = (lo + hi) / 2;
const file_slot *f = &s->files[mid];
if (off < f->start) hi = mid;
else if (off >= f->start + f->length) lo = mid + 1;
else return f;
}
return NULL;
}
static naut_err io_at(naut_storage *s, int64_t offset, void *buf, size_t len, bool write) {
if (offset < 0 || (int64_t)(offset + (int64_t)len) > s->total) return NAUT_ERR_RANGE;
uint8_t *p = buf;
while (len > 0) {
const file_slot *f = locate(s, offset);
if (!f) return NAUT_ERR_RANGE; /* zero-length file region */
if (f->externalized) return NAUT_ERR_RANGE; /* moved out; not backed here */
off_t fo = (off_t)(offset - f->start);
size_t chunk = len;
int64_t avail = f->length - fo;
if ((int64_t)chunk > avail) chunk = (size_t)avail;
if (chunk == 0) { offset++; continue; } /* skip past empty file */
bool aligned = f->direct_fd >= 0 &&
((uintptr_t)p & (NAUT_PAGE - 1)) == 0 &&
((uint64_t)fo & (NAUT_PAGE - 1)) == 0 &&
(chunk & (NAUT_PAGE - 1)) == 0;
int io_fd = aligned ? f->direct_fd : f->fd;
ssize_t done = write ? pwrite(io_fd, p, chunk, fo)
: pread(io_fd, p, chunk, fo);
if (done < 0) {
if (errno == EINTR) continue;
return NAUT_ERR_IO;
}
if (done == 0 && !write) return NAUT_ERR_IO; /* unexpected EOF */
p += done; offset += done; len -= (size_t)done;
}
return NAUT_OK;
}
naut_err naut_storage_write(naut_storage *s, int64_t offset, const void *buf, size_t len) {
return io_at(s, offset, (void *)buf, len, true);
}
naut_err naut_storage_read(naut_storage *s, int64_t offset, void *buf, size_t len) {
return io_at(s, offset, buf, len, false);
}
static naut_err copy_file(const char *src, const char *dst) {
int in = open(src, O_RDONLY);
if (in < 0) return NAUT_ERR_IO;
int out = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (out < 0) { close(in); return NAUT_ERR_IO; }
naut_err e = NAUT_OK;
char buf[1 << 16];
for (;;) {
ssize_t r = read(in, buf, sizeof buf);
if (r < 0) { if (errno == EINTR) continue; e = NAUT_ERR_IO; break; }
if (r == 0) break;
for (ssize_t off = 0; off < r; ) {
ssize_t w = write(out, buf + off, (size_t)(r - off));
if (w < 0) { if (errno == EINTR) continue; e = NAUT_ERR_IO; goto done; }
off += w;
}
}
done:
close(in); close(out);
return e;
}
naut_err naut_storage_relocate(naut_storage *s, size_t file_index, const char *dest) {
if (file_index >= s->nfiles) return NAUT_ERR_RANGE;
file_slot *f = &s->files[file_index];
if (f->externalized) return NAUT_ERR_INVAL;
if (f->direct_fd >= 0) {
fsync(f->direct_fd);
close(f->direct_fd);
f->direct_fd = -1;
}
if (f->fd >= 0) { fsync(f->fd); close(f->fd); f->fd = -1; }
/* ensure the destination directory exists */
char dcopy[4096];
if ((size_t)snprintf(dcopy, sizeof dcopy, "%s", dest) >= sizeof dcopy) return NAUT_ERR_INVAL;
if (make_parents(dcopy) != NAUT_OK) return NAUT_ERR_IO;
if (rename(f->path, dest) != 0) {
if (errno != EXDEV) { NAUT_ERROR("rename %s -> %s: %s", f->path, dest, strerror(errno)); return NAUT_ERR_IO; }
naut_err e = copy_file(f->path, dest); /* cross-filesystem */
if (e != NAUT_OK) return e;
if (unlink(f->path) != 0) NAUT_WARN("unlink %s after copy: %s", f->path, strerror(errno));
}
f->externalized = true;
NAUT_INFO("relocated file %zu -> %s", file_index, dest);
return NAUT_OK;
}
naut_err naut_storage_sync(naut_storage *s) {
for (size_t i = 0; i < s->nfiles; i++)
if ((s->files[i].direct_fd >= 0 &&
fsync(s->files[i].direct_fd) != 0) ||
(s->files[i].fd >= 0 &&
fsync(s->files[i].fd) != 0))
return NAUT_ERR_IO;
return NAUT_OK;
}
int64_t naut_storage_total(const naut_storage *s) { return s->total; }
bool naut_storage_direct_enabled(const naut_storage *s) {
return s && s->direct_enabled;
}

162
src/tracker/fetch.c Normal file
View file

@ -0,0 +1,162 @@
#include "naut/tracker.h"
#include "naut/log.h"
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/time.h>
#define TRACKER_RESPONSE_MAX (16u << 20)
static int dial(const char *host, const char *port, int socktype) {
struct addrinfo hints, *res = NULL, *ai;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET; /* IPv4 for now (compact peers are v4) */
hints.ai_socktype = socktype;
if (getaddrinfo(host, port, &hints, &res) != 0) return -1;
int fd = -1;
for (ai = res; ai; ai = ai->ai_next) {
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd < 0) continue;
struct timeval tv = { .tv_sec = 10, .tv_usec = 0 };
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) break;
close(fd); fd = -1;
}
freeaddrinfo(res);
return fd;
}
/* split "http://host[:port]/path" */
static bool parse_http_url(const char *url, char *host, size_t hostsz,
char *port, size_t portsz, const char **path) {
if (strncmp(url, "http://", 7) != 0) return false;
const char *h = url + 7;
const char *slash = strchr(h, '/');
const char *hostend = slash ? slash : h + strlen(h);
const char *colon = memchr(h, ':', (size_t)(hostend - h));
size_t hlen = colon ? (size_t)(colon - h) : (size_t)(hostend - h);
if (hlen >= hostsz) return false;
memcpy(host, h, hlen); host[hlen] = 0;
if (colon) {
size_t plen = (size_t)(hostend - colon - 1);
if (plen >= portsz) return false;
memcpy(port, colon + 1, plen); port[plen] = 0;
} else { snprintf(port, portsz, "80"); }
*path = slash ? slash : "/";
return true;
}
static bool write_all(int fd, const void *data, size_t len) {
const uint8_t *p = data;
while (len) {
ssize_t n = write(fd, p, len);
if (n < 0) {
if (errno == EINTR) continue;
return false;
}
p += (size_t)n;
len -= (size_t)n;
}
return true;
}
naut_err naut_tracker_announce_http(const char *url, naut_tracker_response *out) {
char host[256], port[16]; const char *path;
if (!parse_http_url(url, host, sizeof host, port, sizeof port, &path))
return NAUT_ERR_INVAL;
int fd = dial(host, port, SOCK_STREAM);
if (fd < 0) { NAUT_WARN("tracker connect %s:%s failed", host, port); return NAUT_ERR_IO; }
char req[2048];
int rn = snprintf(req, sizeof req,
"GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Naut/0.1\r\nAccept: */*\r\n\r\n",
path, host);
if (rn < 0 || (size_t)rn >= sizeof req ||
!write_all(fd, req, (size_t)rn)) {
close(fd);
return NAUT_ERR_IO;
}
/* read whole response (server closes on HTTP/1.0) */
size_t cap = 1 << 16, len = 0;
uint8_t *buf = malloc(cap);
if (!buf) { close(fd); return NAUT_ERR_NOMEM; }
naut_err read_error = NAUT_OK;
for (;;) {
if (len == cap) {
if (cap == TRACKER_RESPONSE_MAX) {
read_error = NAUT_ERR_FULL;
break;
}
size_t next_cap = NAUT_MIN(cap * 2, (size_t)TRACKER_RESPONSE_MAX);
uint8_t *next = realloc(buf, next_cap);
if (!next) {
read_error = NAUT_ERR_NOMEM;
break;
}
buf = next;
cap = next_cap;
}
ssize_t r = read(fd, buf + len, cap - len);
if (r < 0) {
if (errno == EINTR) continue;
read_error = NAUT_ERR_IO;
break;
}
if (r == 0) break;
len += (size_t)r;
}
close(fd);
if (read_error != NAUT_OK) {
free(buf);
return read_error;
}
/* find body after CRLFCRLF */
uint8_t *body = NULL; size_t blen = 0;
for (size_t i = 0; i + 3 < len; i++)
if (buf[i]=='\r'&&buf[i+1]=='\n'&&buf[i+2]=='\r'&&buf[i+3]=='\n') {
body = buf + i + 4; blen = len - (i + 4); break;
}
bool success = len >= 12 && memcmp(buf, "HTTP/", 5) == 0 &&
buf[9] == '2';
naut_err e = success && body
? naut_tracker_parse_http(body, blen, out)
: NAUT_ERR_PROTO;
free(buf);
return e;
}
naut_err naut_tracker_announce_udp(const char *host, uint16_t port,
const naut_announce_req *req,
naut_tracker_response *out) {
char portstr[16]; snprintf(portstr, sizeof portstr, "%u", port);
int fd = dial(host, portstr, SOCK_DGRAM);
if (fd < 0) return NAUT_ERR_IO;
srand((unsigned)time(NULL) ^ (unsigned)getpid());
uint32_t txid = (uint32_t)rand();
uint8_t pkt[98], resp[1500];
naut_udp_build_connect(pkt, txid);
if (write(fd, pkt, 16) != 16) { close(fd); return NAUT_ERR_IO; }
ssize_t r = read(fd, resp, sizeof resp);
uint64_t cid;
if (r < 0 || naut_udp_parse_connect(resp, (size_t)r, txid, &cid) != NAUT_OK) {
close(fd); return NAUT_ERR_IO;
}
txid++;
naut_udp_build_announce(pkt, cid, txid, req);
if (write(fd, pkt, 98) != 98) { close(fd); return NAUT_ERR_IO; }
r = read(fd, resp, sizeof resp);
naut_err e = (r < 0) ? NAUT_ERR_IO
: naut_udp_parse_announce(resp, (size_t)r, txid, out);
close(fd);
return e;
}

120
src/tracker/tracker.c Normal file
View file

@ -0,0 +1,120 @@
#include "naut/tracker.h"
#include "naut/bencode.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void naut_tracker_response_free(naut_tracker_response *r) {
free(r->peers); r->peers = NULL; r->num_peers = 0;
free(r->failure); r->failure = NULL;
}
/* percent-encode raw bytes per RFC 3986 (unreserved chars pass through) */
static size_t pct_encode(const uint8_t *in, size_t n, char *out, size_t outsz) {
static const char hx[] = "0123456789ABCDEF";
size_t o = 0;
for (size_t i = 0; i < n; i++) {
uint8_t c = in[i];
bool unreserved = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' ||
c == '.' || c == '~';
if (unreserved) {
if (o + 1 >= outsz) return 0;
out[o++] = (char)c;
} else {
if (o + 3 >= outsz) return 0;
out[o++] = '%'; out[o++] = hx[c >> 4]; out[o++] = hx[c & 15];
}
}
return o;
}
size_t naut_tracker_http_url(const char *base, const naut_announce_req *req,
char *out, size_t outsz) {
static const char *ev[] = { "", "completed", "started", "stopped" };
if (req->event < NAUT_TEV_NONE || req->event > NAUT_TEV_STOPPED) return 0;
char ih[61], pid[61]; /* 20*3 = 60 worst case + NUL */
size_t ihn = pct_encode(req->info_hash, 20, ih, sizeof ih);
size_t pidn = pct_encode(req->peer_id, 20, pid, sizeof pid);
if (!ihn || !pidn) return 0;
ih[ihn] = 0; pid[pidn] = 0;
const char *sep = strchr(base, '?') ? "&" : "?";
int n = snprintf(out, outsz,
"%s%sinfo_hash=%s&peer_id=%s&port=%u&uploaded=%llu&downloaded=%llu"
"&left=%llu&compact=1&numwant=%d%s%s&key=%u",
base, sep, ih, pid, req->port,
(unsigned long long)req->uploaded, (unsigned long long)req->downloaded,
(unsigned long long)req->left, req->numwant < 0 ? 50 : req->numwant,
req->event ? "&event=" : "", ev[req->event], req->key);
if (n < 0 || (size_t)n >= outsz) return 0;
return (size_t)n;
}
static naut_err parse_peers(const naut_bc *peers, naut_tracker_response *out) {
const uint8_t *p; size_t n;
if (naut_bc_get_str(peers, &p, &n)) { /* compact: 6 bytes each */
if (n % 6 != 0) return NAUT_ERR_PROTO;
out->num_peers = n / 6;
out->peers = calloc(out->num_peers ? out->num_peers : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < out->num_peers; i++) {
memcpy(out->peers[i].ip, p + i*6, 4);
out->peers[i].port = ((uint16_t)p[i*6+4] << 8) | p[i*6+5];
}
return NAUT_OK;
}
if (peers && peers->type == NAUT_BC_LIST) { /* dict form */
out->peers = calloc(peers->v.list.count ? peers->v.list.count : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < peers->v.list.count; i++) {
const naut_bc *pe = naut_bc_list_at(peers, i);
const uint8_t *ips; size_t ipn; int64_t port;
if (!naut_bc_get_str(naut_bc_dict_get(pe, "ip"), &ips, &ipn)) continue;
if (!naut_bc_get_int(naut_bc_dict_get(pe, "port"), &port)) continue;
unsigned a, b, c, dd;
char tmp[64];
if (ipn >= sizeof tmp) continue;
memcpy(tmp, ips, ipn); tmp[ipn] = 0;
if (sscanf(tmp, "%u.%u.%u.%u", &a, &b, &c, &dd) != 4) continue;
if (a > 255 || b > 255 || c > 255 || dd > 255 ||
port <= 0 || port > UINT16_MAX) continue;
naut_peer_addr *pa = &out->peers[out->num_peers++];
pa->ip[0]=(uint8_t)a; pa->ip[1]=(uint8_t)b; pa->ip[2]=(uint8_t)c; pa->ip[3]=(uint8_t)dd;
pa->port = (uint16_t)port;
}
return NAUT_OK;
}
return NAUT_ERR_PROTO;
}
naut_err naut_tracker_parse_http(const uint8_t *body, size_t len,
naut_tracker_response *out) {
memset(out, 0, sizeof(*out));
out->seeders = out->leechers = -1;
naut_bc_doc *doc = NULL;
naut_err e = naut_bc_parse(body, len, &doc);
if (e != NAUT_OK) return e;
const naut_bc *root = naut_bc_root(doc);
const uint8_t *fp; size_t fn;
if (naut_bc_get_str(naut_bc_dict_get(root, "failure reason"), &fp, &fn)) {
out->failure = malloc(fn + 1);
if (out->failure) { memcpy(out->failure, fp, fn); out->failure[fn] = 0; }
naut_bc_free(doc);
return NAUT_ERR_PROTO; /* tracker reported failure */
}
int64_t iv = 0;
naut_bc_get_int(naut_bc_dict_get(root, "interval"), &iv);
out->interval = (int32_t)iv;
int64_t sc;
if (naut_bc_get_int(naut_bc_dict_get(root, "complete"), &sc)) out->seeders = (int32_t)sc;
if (naut_bc_get_int(naut_bc_dict_get(root, "incomplete"), &sc)) out->leechers = (int32_t)sc;
e = parse_peers(naut_bc_dict_get(root, "peers"), out);
naut_bc_free(doc);
if (e != NAUT_OK) { naut_tracker_response_free(out); return e; }
return NAUT_OK;
}

81
src/tracker/udp.c Normal file
View file

@ -0,0 +1,81 @@
#include "naut/tracker.h"
#include <stdlib.h>
#include <string.h>
#define UDP_PROTOCOL_ID 0x41727101980ULL /* BEP-15 magic */
#define ACTION_CONNECT 0
#define ACTION_ANNOUNCE 1
#define ACTION_ERROR 3
static void wr16(uint8_t *p, uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; }
static void wr32(uint8_t *p, uint32_t v) {
p[0]=(uint8_t)(v>>24); p[1]=(uint8_t)(v>>16); p[2]=(uint8_t)(v>>8); p[3]=(uint8_t)v;
}
static void wr64(uint8_t *p, uint64_t v) { wr32(p, (uint32_t)(v>>32)); wr32(p+4, (uint32_t)v); }
static uint32_t rd32(const uint8_t *p) {
return ((uint32_t)p[0]<<24)|((uint32_t)p[1]<<16)|((uint32_t)p[2]<<8)|p[3];
}
static uint64_t rd64(const uint8_t *p) { return ((uint64_t)rd32(p)<<32) | rd32(p+4); }
void naut_udp_build_connect(uint8_t out[16], uint32_t txid) {
wr64(out, UDP_PROTOCOL_ID);
wr32(out + 8, ACTION_CONNECT);
wr32(out + 12, txid);
}
naut_err naut_udp_parse_connect(const uint8_t *in, size_t len, uint32_t txid,
uint64_t *connection_id) {
if (len < 16) return NAUT_ERR_PROTO;
if (rd32(in) != ACTION_CONNECT) return NAUT_ERR_PROTO;
if (rd32(in + 4) != txid) return NAUT_ERR_PROTO;
*connection_id = rd64(in + 8);
return NAUT_OK;
}
void naut_udp_build_announce(uint8_t out[98], uint64_t connection_id,
uint32_t txid, const naut_announce_req *req) {
wr64(out + 0, connection_id);
wr32(out + 8, ACTION_ANNOUNCE);
wr32(out + 12, txid);
memcpy(out + 16, req->info_hash, 20);
memcpy(out + 36, req->peer_id, 20);
wr64(out + 56, req->downloaded);
wr64(out + 64, req->left);
wr64(out + 72, req->uploaded);
wr32(out + 80, (uint32_t)req->event);
wr32(out + 84, 0); /* IP: 0 = source */
wr32(out + 88, req->key);
wr32(out + 92, (uint32_t)(req->numwant < 0 ? 50 : req->numwant));
wr16(out + 96, req->port);
}
naut_err naut_udp_parse_announce(const uint8_t *in, size_t len, uint32_t txid,
naut_tracker_response *out) {
memset(out, 0, sizeof(*out));
out->seeders = out->leechers = -1;
if (len < 8) return NAUT_ERR_PROTO;
uint32_t action = rd32(in);
if (rd32(in + 4) != txid) return NAUT_ERR_PROTO;
if (action == ACTION_ERROR) {
size_t mn = len - 8;
out->failure = malloc(mn + 1);
if (out->failure) { memcpy(out->failure, in + 8, mn); out->failure[mn] = 0; }
return NAUT_ERR_PROTO;
}
if (action != ACTION_ANNOUNCE || len < 20 || (len - 20) % 6 != 0)
return NAUT_ERR_PROTO;
out->interval = (int32_t)rd32(in + 8);
out->leechers = (int32_t)rd32(in + 12);
out->seeders = (int32_t)rd32(in + 16);
size_t avail = (len - 20) / 6;
out->peers = calloc(avail ? avail : 1, sizeof(naut_peer_addr));
if (!out->peers) return NAUT_ERR_NOMEM;
for (size_t i = 0; i < avail; i++) {
const uint8_t *p = in + 20 + i*6;
memcpy(out->peers[i].ip, p, 4);
out->peers[i].port = ((uint16_t)p[4] << 8) | p[5];
}
out->num_peers = avail;
return NAUT_OK;
}