Initial commit: Naut-Torrent — from-scratch 10 GbE BitTorrent client
A maintainable, extensible BitTorrent client (C11, Linux/io_uring) targeting 10 GbE saturation. All torrent functionality is built from scratch; liburing is the only linked third-party dependency on the data path. Implements Phases 1-7 of the roadmap: - core: page-aligned buffer pool, MPMC/Treiber queues, bitfields, worker pool - crypto: SHA-1/256 (SHA-NI + scalar), Merkle (BEP-52), RC4 (MSE) - bencode/metainfo: zero-copy parser, v1/v2/hybrid .torrent + magnet - peer: sans-IO wire codec, MSE/PE handshake state machine, BEP-10, ut_metadata, PEX - piece/storage: block-level multi-peer engine, rarest-first + endgame, per-file completion events + single-file relocate (move-as-you-finish) - tracker/dht: HTTP + UDP (BEP-15) trackers, BEP-5 KRPC iterative lookup - platform: io_uring reactor (SQPOLL, registered buffers, SEND_ZC) - surface: versioned RPC, native plugin ABI, sandboxed Lua scripting, nautd/nautctl Verified against libtorrent (single/multi/hybrid, MSE, magnet-via-DHT, swarm); unit + interop tests green; ASan/UBSan/TSan clean. Scripting reference in docs/scripting.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
2178d6a70c
121 changed files with 12644 additions and 0 deletions
310
src/bencode/bencode.c
Normal file
310
src/bencode/bencode.c
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
#include "naut/bencode.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_DEPTH 100
|
||||
#define MAX_NODES (4u * 1024 * 1024) /* hard cap on tree size */
|
||||
|
||||
/* --- arena: stable-address bump allocator -------------------------------- */
|
||||
typedef struct arena_chunk {
|
||||
struct arena_chunk *next;
|
||||
size_t used, cap;
|
||||
uint8_t data[];
|
||||
} arena_chunk;
|
||||
|
||||
struct naut_bc_doc {
|
||||
arena_chunk *chunks;
|
||||
naut_bc *root;
|
||||
size_t nodes;
|
||||
};
|
||||
|
||||
static void *arena_alloc(naut_bc_doc *d, size_t n) {
|
||||
n = (n + 7) & ~(size_t)7;
|
||||
arena_chunk *c = d->chunks;
|
||||
if (!c || c->used + n > c->cap) {
|
||||
size_t cap = n > 65536 ? n : 65536;
|
||||
c = malloc(sizeof(arena_chunk) + cap);
|
||||
if (!c) return NULL;
|
||||
c->next = d->chunks; c->used = 0; c->cap = cap;
|
||||
d->chunks = c;
|
||||
}
|
||||
void *p = c->data + c->used;
|
||||
c->used += n;
|
||||
return p;
|
||||
}
|
||||
|
||||
/* --- parser -------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
const uint8_t *p, *end;
|
||||
naut_bc_doc *doc;
|
||||
naut_err err;
|
||||
} P;
|
||||
|
||||
/* temporary growable vector of naut_bc used while a container's size is unknown */
|
||||
typedef struct { naut_bc *v; size_t n, cap; } vec;
|
||||
static bool vec_push(vec *x, naut_bc item) {
|
||||
if (x->n == x->cap) {
|
||||
size_t nc = x->cap ? x->cap * 2 : 8;
|
||||
naut_bc *nv = realloc(x->v, nc * sizeof(naut_bc));
|
||||
if (!nv) return false;
|
||||
x->v = nv; x->cap = nc;
|
||||
}
|
||||
x->v[x->n++] = item;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_value(P *s, naut_bc *out, int depth);
|
||||
|
||||
static bool parse_uint(P *s, size_t *out, uint8_t term) {
|
||||
/* decimal, no leading zeros (except a lone "0"), terminated by `term` */
|
||||
if (s->p >= s->end) return false;
|
||||
size_t val = 0;
|
||||
const uint8_t *start = s->p;
|
||||
if (*s->p == '0') { /* only "0" then term */
|
||||
s->p++;
|
||||
if (s->p >= s->end || *s->p != term) return false;
|
||||
*out = 0; s->p++;
|
||||
return true;
|
||||
}
|
||||
while (s->p < s->end && *s->p >= '0' && *s->p <= '9') {
|
||||
if (val > (SIZE_MAX - 9) / 10) return false; /* overflow guard */
|
||||
val = val * 10 + (size_t)(*s->p - '0');
|
||||
s->p++;
|
||||
}
|
||||
if (s->p == start) return false; /* no digits */
|
||||
if (s->p >= s->end || *s->p != term) return false;
|
||||
s->p++;
|
||||
*out = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_int(P *s, naut_bc *out) {
|
||||
s->p++; /* 'i' */
|
||||
bool neg = false;
|
||||
if (s->p < s->end && *s->p == '-') { neg = true; s->p++; }
|
||||
/* digits up to 'e', no leading zero, no "-0" */
|
||||
if (s->p >= s->end) return false;
|
||||
const uint8_t *d0 = s->p;
|
||||
int64_t val = 0;
|
||||
if (*s->p == '0') {
|
||||
s->p++;
|
||||
if (s->p >= s->end || *s->p != 'e') return false; /* "i0e" only */
|
||||
if (neg) return false; /* "-0" illegal */
|
||||
} else {
|
||||
while (s->p < s->end && *s->p >= '0' && *s->p <= '9') {
|
||||
if (val > (INT64_MAX - 9) / 10) return false;
|
||||
val = val * 10 + (*s->p - '0');
|
||||
s->p++;
|
||||
}
|
||||
if (s->p == d0) return false;
|
||||
if (s->p >= s->end || *s->p != 'e') return false;
|
||||
}
|
||||
s->p++; /* 'e' */
|
||||
out->type = NAUT_BC_INT;
|
||||
out->v.i = neg ? -val : val;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_string(P *s, naut_bc *out) {
|
||||
size_t n;
|
||||
if (!parse_uint(s, &n, ':')) return false;
|
||||
if ((size_t)(s->end - s->p) < n) return false;
|
||||
out->type = NAUT_BC_STR;
|
||||
out->v.str.p = s->p;
|
||||
out->v.str.n = n;
|
||||
s->p += n;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_list(P *s, naut_bc *out, int depth) {
|
||||
s->p++; /* 'l' */
|
||||
vec items = {0};
|
||||
while (s->p < s->end && *s->p != 'e') {
|
||||
naut_bc item;
|
||||
if (!parse_value(s, &item, depth + 1)) { free(items.v); return false; }
|
||||
if (!vec_push(&items, item)) { free(items.v); s->err = NAUT_ERR_NOMEM; return false; }
|
||||
}
|
||||
if (s->p >= s->end) { free(items.v); return false; } /* missing 'e' */
|
||||
s->p++;
|
||||
naut_bc *arr = NULL;
|
||||
if (items.n) {
|
||||
arr = arena_alloc(s->doc, items.n * sizeof(naut_bc));
|
||||
if (!arr) { free(items.v); s->err = NAUT_ERR_NOMEM; return false; }
|
||||
memcpy(arr, items.v, items.n * sizeof(naut_bc));
|
||||
}
|
||||
free(items.v);
|
||||
out->type = NAUT_BC_LIST;
|
||||
out->v.list.items = arr;
|
||||
out->v.list.count = items.n;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parse_dict(P *s, naut_bc *out, int depth) {
|
||||
s->p++; /* 'd' */
|
||||
naut_bc_pair *pairs = NULL; size_t n = 0, cap = 0;
|
||||
while (s->p < s->end && *s->p != 'e') {
|
||||
naut_bc key;
|
||||
if (s->p >= s->end || *s->p < '0' || *s->p > '9') goto fail; /* key must be string */
|
||||
if (!parse_string(s, &key)) goto fail;
|
||||
naut_bc *val = arena_alloc(s->doc, sizeof(naut_bc));
|
||||
if (!val) { s->err = NAUT_ERR_NOMEM; goto fail; }
|
||||
if (!parse_value(s, val, depth + 1)) goto fail;
|
||||
if (n == cap) {
|
||||
size_t nc = cap ? cap * 2 : 8;
|
||||
naut_bc_pair *np = realloc(pairs, nc * sizeof(*np));
|
||||
if (!np) { s->err = NAUT_ERR_NOMEM; goto fail; }
|
||||
pairs = np; cap = nc;
|
||||
}
|
||||
pairs[n].kp = key.v.str.p; pairs[n].kn = key.v.str.n; pairs[n].val = val;
|
||||
n++;
|
||||
}
|
||||
if (s->p >= s->end) goto fail; /* missing 'e' */
|
||||
s->p++;
|
||||
{
|
||||
naut_bc_pair *arr = NULL;
|
||||
if (n) {
|
||||
arr = arena_alloc(s->doc, n * sizeof(naut_bc_pair));
|
||||
if (!arr) { s->err = NAUT_ERR_NOMEM; goto fail; }
|
||||
memcpy(arr, pairs, n * sizeof(naut_bc_pair));
|
||||
}
|
||||
free(pairs);
|
||||
out->type = NAUT_BC_DICT;
|
||||
out->v.dict.pairs = arr;
|
||||
out->v.dict.count = n;
|
||||
return true;
|
||||
}
|
||||
fail:
|
||||
free(pairs);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parse_value(P *s, naut_bc *out, int depth) {
|
||||
if (depth > MAX_DEPTH) { s->err = NAUT_ERR_PROTO; return false; }
|
||||
if (++s->doc->nodes > MAX_NODES) { s->err = NAUT_ERR_PROTO; return false; }
|
||||
if (s->p >= s->end) return false;
|
||||
const uint8_t *raw0 = s->p;
|
||||
bool ok;
|
||||
switch (*s->p) {
|
||||
case 'i': ok = parse_int(s, out); break;
|
||||
case 'l': ok = parse_list(s, out, depth); break;
|
||||
case 'd': ok = parse_dict(s, out, depth); break;
|
||||
default:
|
||||
if (*s->p >= '0' && *s->p <= '9') ok = parse_string(s, out);
|
||||
else { s->err = NAUT_ERR_PROTO; return false; }
|
||||
}
|
||||
if (ok) { out->raw = raw0; out->raw_len = (size_t)(s->p - raw0); }
|
||||
return ok;
|
||||
}
|
||||
|
||||
naut_err naut_bc_parse_prefix(const uint8_t *data, size_t len,
|
||||
naut_bc_doc **out, size_t *consumed) {
|
||||
if (!data || !out || !consumed) return NAUT_ERR_INVAL;
|
||||
naut_bc_doc *doc = calloc(1, sizeof(*doc));
|
||||
if (!doc) return NAUT_ERR_NOMEM;
|
||||
|
||||
P s = { .p = data, .end = data + len, .doc = doc, .err = NAUT_ERR_PROTO };
|
||||
naut_bc *root = arena_alloc(doc, sizeof(naut_bc));
|
||||
if (!root) { naut_bc_free(doc); return NAUT_ERR_NOMEM; }
|
||||
|
||||
if (!parse_value(&s, root, 0)) {
|
||||
naut_err e = s.err ? s.err : NAUT_ERR_PROTO;
|
||||
naut_bc_free(doc);
|
||||
return e;
|
||||
}
|
||||
doc->root = root;
|
||||
*out = doc;
|
||||
*consumed = (size_t)(s.p - data);
|
||||
return NAUT_OK;
|
||||
}
|
||||
|
||||
naut_err naut_bc_parse(const uint8_t *data, size_t len, naut_bc_doc **out) {
|
||||
size_t consumed = 0;
|
||||
naut_err e = naut_bc_parse_prefix(data, len, out, &consumed);
|
||||
if (e != NAUT_OK) return e;
|
||||
if (consumed != len) {
|
||||
naut_bc_free(*out);
|
||||
*out = NULL;
|
||||
return NAUT_ERR_PROTO;
|
||||
}
|
||||
return NAUT_OK;
|
||||
}
|
||||
|
||||
const naut_bc *naut_bc_root(const naut_bc_doc *doc) { return doc ? doc->root : NULL; }
|
||||
|
||||
void naut_bc_free(naut_bc_doc *doc) {
|
||||
if (!doc) return;
|
||||
arena_chunk *c = doc->chunks;
|
||||
while (c) { arena_chunk *n = c->next; free(c); c = n; }
|
||||
free(doc);
|
||||
}
|
||||
|
||||
/* --- accessors ----------------------------------------------------------- */
|
||||
const naut_bc *naut_bc_dict_get(const naut_bc *d, const char *key) {
|
||||
if (!d || d->type != NAUT_BC_DICT) return NULL;
|
||||
size_t klen = strlen(key);
|
||||
for (size_t i = 0; i < d->v.dict.count; i++) {
|
||||
const naut_bc_pair *p = &d->v.dict.pairs[i];
|
||||
if (p->kn == klen && memcmp(p->kp, key, klen) == 0) return p->val;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const naut_bc *naut_bc_list_at(const naut_bc *l, size_t i) {
|
||||
if (!l || l->type != NAUT_BC_LIST || i >= l->v.list.count) return NULL;
|
||||
return &l->v.list.items[i];
|
||||
}
|
||||
|
||||
bool naut_bc_get_int(const naut_bc *v, int64_t *out) {
|
||||
if (!v || v->type != NAUT_BC_INT) { *out = 0; return false; }
|
||||
*out = v->v.i; return true;
|
||||
}
|
||||
|
||||
bool naut_bc_get_str(const naut_bc *v, const uint8_t **p, size_t *n) {
|
||||
if (!v || v->type != NAUT_BC_STR) { *p = NULL; *n = 0; return false; }
|
||||
*p = v->v.str.p; *n = v->v.str.n; return true;
|
||||
}
|
||||
|
||||
bool naut_bc_str_eq(const naut_bc *v, const char *s) {
|
||||
if (!v || v->type != NAUT_BC_STR) return false;
|
||||
size_t n = strlen(s);
|
||||
return v->v.str.n == n && memcmp(v->v.str.p, s, n) == 0;
|
||||
}
|
||||
|
||||
/* --- encoder ------------------------------------------------------------- */
|
||||
void naut_bc_w_init(naut_bc_writer *w) { memset(w, 0, sizeof(*w)); }
|
||||
void naut_bc_w_free(naut_bc_writer *w) { free(w->buf); memset(w, 0, sizeof(*w)); }
|
||||
|
||||
static bool w_reserve(naut_bc_writer *w, size_t extra) {
|
||||
if (w->err) return false;
|
||||
if (w->len + extra > w->cap) {
|
||||
size_t nc = w->cap ? w->cap * 2 : 256;
|
||||
while (nc < w->len + extra) nc *= 2;
|
||||
uint8_t *nb = realloc(w->buf, nc);
|
||||
if (!nb) { w->err = NAUT_ERR_NOMEM; return false; }
|
||||
w->buf = nb; w->cap = nc;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static void w_putc(naut_bc_writer *w, char c) {
|
||||
if (w_reserve(w, 1)) w->buf[w->len++] = (uint8_t)c;
|
||||
}
|
||||
static void w_raw(naut_bc_writer *w, const void *p, size_t n) {
|
||||
if (w_reserve(w, n)) { memcpy(w->buf + w->len, p, n); w->len += n; }
|
||||
}
|
||||
static void w_decimal(naut_bc_writer *w, int64_t v) {
|
||||
char tmp[24];
|
||||
int n = snprintf(tmp, sizeof tmp, "%lld", (long long)v);
|
||||
w_raw(w, tmp, (size_t)n);
|
||||
}
|
||||
|
||||
void naut_bc_w_int(naut_bc_writer *w, int64_t v) {
|
||||
w_putc(w, 'i'); w_decimal(w, v); w_putc(w, 'e');
|
||||
}
|
||||
void naut_bc_w_bytes(naut_bc_writer *w, const void *p, size_t n) {
|
||||
w_decimal(w, (int64_t)n); w_putc(w, ':'); w_raw(w, p, n);
|
||||
}
|
||||
void naut_bc_w_cstr(naut_bc_writer *w, const char *s) { naut_bc_w_bytes(w, s, strlen(s)); }
|
||||
void naut_bc_w_list_begin(naut_bc_writer *w) { w_putc(w, 'l'); }
|
||||
void naut_bc_w_dict_begin(naut_bc_writer *w) { w_putc(w, 'd'); }
|
||||
void naut_bc_w_end(naut_bc_writer *w) { w_putc(w, 'e'); }
|
||||
98
src/core/bitfield.c
Normal file
98
src/core/bitfield.c
Normal 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
154
src/core/buf.c
Normal 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
18
src/core/common.c
Normal 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
83
src/core/log.c
Normal 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
69
src/core/mpmc.c
Normal 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
142
src/core/worker.c
Normal 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
59
src/crypto/merkle.c
Normal 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
34
src/crypto/rc4.c
Normal 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
72
src/crypto/sha1.c
Normal 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
202
src/crypto/sha256.c
Normal 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
227
src/dht/dht.c
Normal 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
154
src/dht/fetch.c
Normal 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
116
src/metainfo/magnet.c
Normal 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
289
src/metainfo/metainfo.c
Normal 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
296
src/peer/extension.c
Normal 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
235
src/peer/metadata.c
Normal 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
466
src/peer/mse.c
Normal 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
64
src/peer/pipeline.c
Normal 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
113
src/peer/wire.c
Normal 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
385
src/piece/piece.c
Normal 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
62
src/platform/net.c
Normal 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
19
src/platform/system.c
Normal 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
151
src/platform/uring.c
Normal 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
354
src/plugin/plugin.c
Normal 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
261
src/rpc/rpc.c
Normal 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(®istry->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(®istry->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(®istry->lock);
|
||||
for (size_t i = 0; i < registry->count; i++) {
|
||||
if (strcmp(registry->commands[i].method, method) == 0) {
|
||||
pthread_mutex_unlock(®istry->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(®istry->lock);
|
||||
return NAUT_ERR_NOMEM;
|
||||
}
|
||||
registry->commands = next;
|
||||
registry->capacity = capacity;
|
||||
}
|
||||
char *copy = strdup(method);
|
||||
if (!copy) {
|
||||
pthread_mutex_unlock(®istry->lock);
|
||||
return NAUT_ERR_NOMEM;
|
||||
}
|
||||
registry->commands[registry->count++] = (command) {
|
||||
.method = copy,
|
||||
.handler = handler,
|
||||
.context = context,
|
||||
};
|
||||
pthread_mutex_unlock(®istry->lock);
|
||||
return NAUT_OK;
|
||||
}
|
||||
|
||||
void naut_rpc_unregister(naut_rpc_registry *registry, const char *method) {
|
||||
if (!registry || !method) return;
|
||||
pthread_mutex_lock(®istry->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(®istry->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(®istry->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(®istry->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
312
src/script/script.c
Normal 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
111
src/session/event.c
Normal 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
71
src/session/session.c
Normal 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
237
src/storage/storage.c
Normal 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
162
src/tracker/fetch.c
Normal 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
120
src/tracker/tracker.c
Normal 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
81
src/tracker/udp.c
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue