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

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

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

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

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

77
include/naut/bencode.h Normal file
View file

@ -0,0 +1,77 @@
/* bencode.h — bencode parser/encoder for .torrent, tracker, and DHT messages.
*
* The parser is zero-copy: strings are slices into the caller's buffer, never
* duplicated. Every parsed value also records its exact raw byte span, which is
* what lets us compute an info-hash over the *original* encoding of the `info`
* dict without re-serializing (re-serialization would risk a non-canonical
* byte sequence and a wrong hash). Container children live in an arena owned by
* the document, so the whole tree frees in one shot.
*
* Hardened for hostile input (it parses bytes straight off the wire): explicit
* depth and node-count limits, strict integer/string syntax, no unbounded
* recursion blow-up. This is a primary fuzz target.
*/
#ifndef NAUT_BENCODE_H
#define NAUT_BENCODE_H
#include "naut/common.h"
typedef enum {
NAUT_BC_INT, NAUT_BC_STR, NAUT_BC_LIST, NAUT_BC_DICT
} naut_bc_type;
typedef struct naut_bc naut_bc;
typedef struct {
const uint8_t *kp; size_t kn; /* key slice (bencode dict keys are strings) */
const naut_bc *val;
} naut_bc_pair;
struct naut_bc {
naut_bc_type type;
union {
int64_t i;
struct { const uint8_t *p; size_t n; } str;
struct { const naut_bc *items; size_t count; } list;
struct { const naut_bc_pair *pairs; size_t count; } dict;
} v;
const uint8_t *raw; /* exact encoding span of this value... */
size_t raw_len; /* ...used for info-hash over the original bytes */
};
typedef struct naut_bc_doc naut_bc_doc;
/* Parse the entire buffer as one bencode value. Rejects trailing garbage.
* On success *out owns the tree (free with naut_bc_free). */
naut_err naut_bc_parse(const uint8_t *data, size_t len, naut_bc_doc **out);
/* Parse one value from the start of a larger buffer. `consumed` receives the
* encoded value length; trailing bytes remain owned by the caller. */
naut_err naut_bc_parse_prefix(const uint8_t *data, size_t len,
naut_bc_doc **out, size_t *consumed);
const naut_bc *naut_bc_root(const naut_bc_doc *doc);
void naut_bc_free(naut_bc_doc *doc);
/* Accessors (return NULL / defaults on type mismatch). */
const naut_bc *naut_bc_dict_get(const naut_bc *d, const char *key);
const naut_bc *naut_bc_list_at(const naut_bc *l, size_t i);
bool naut_bc_get_int(const naut_bc *v, int64_t *out);
bool naut_bc_get_str(const naut_bc *v, const uint8_t **p, size_t *n);
/* true if a STR value equals the given C string exactly */
bool naut_bc_str_eq(const naut_bc *v, const char *s);
/* --- encoder (DHT/tracker/.torrent writing): append to a growable buffer --- */
typedef struct {
uint8_t *buf; size_t len, cap;
naut_err err;
} naut_bc_writer;
void naut_bc_w_init(naut_bc_writer *w);
void naut_bc_w_free(naut_bc_writer *w);
void naut_bc_w_int(naut_bc_writer *w, int64_t v);
void naut_bc_w_bytes(naut_bc_writer *w, const void *p, size_t n);
void naut_bc_w_cstr(naut_bc_writer *w, const char *s);
void naut_bc_w_list_begin(naut_bc_writer *w);
void naut_bc_w_dict_begin(naut_bc_writer *w);
void naut_bc_w_end(naut_bc_writer *w); /* closes the current list/dict */
#endif /* NAUT_BENCODE_H */

49
include/naut/bitfield.h Normal file
View file

@ -0,0 +1,49 @@
/* bitfield.h — fixed-size bitset over uint64 words with hardware popcount.
*
* Backs every "set of pieces/blocks" in the engine: a peer's have-set, our own
* completed pieces, the in-flight request map, interested/choked flags. The
* count()/find operations use __builtin_popcountll and __builtin_ctzll so a
* rarest-first picker can scan availability cheaply even for huge torrents.
*/
#ifndef NAUT_BITFIELD_H
#define NAUT_BITFIELD_H
#include "naut/common.h"
typedef struct naut_bitfield {
uint64_t *words;
size_t nbits;
size_t nwords;
} naut_bitfield;
naut_err naut_bitfield_init(naut_bitfield *bf, size_t nbits);
void naut_bitfield_free(naut_bitfield *bf);
NAUT_INLINE bool naut_bitfield_test(const naut_bitfield *bf, size_t i) {
return (bf->words[i >> 6] >> (i & 63)) & 1u;
}
NAUT_INLINE void naut_bitfield_set(naut_bitfield *bf, size_t i) {
bf->words[i >> 6] |= (uint64_t)1 << (i & 63);
}
NAUT_INLINE void naut_bitfield_clear(naut_bitfield *bf, size_t i) {
bf->words[i >> 6] &= ~((uint64_t)1 << (i & 63));
}
void naut_bitfield_set_all(naut_bitfield *bf);
void naut_bitfield_clear_all(naut_bitfield *bf);
/* number of set bits */
size_t naut_bitfield_count(const naut_bitfield *bf);
/* true when all nbits bits are set (torrent complete) */
bool naut_bitfield_all_set(const naut_bitfield *bf);
/* index of first 0 / first 1 bit at or after `from`, or SIZE_MAX if none */
size_t naut_bitfield_find_zero(const naut_bitfield *bf, size_t from);
size_t naut_bitfield_find_set(const naut_bitfield *bf, size_t from);
/* Load/serialize the BEP-3 wire format: MSB-first within each byte. The wire
* order differs from our little-endian word order, so these are not memcpy. */
void naut_bitfield_from_wire(naut_bitfield *bf, const uint8_t *bytes, size_t nbytes);
void naut_bitfield_to_wire(const naut_bitfield *bf, uint8_t *bytes, size_t nbytes);
#endif /* NAUT_BITFIELD_H */

63
include/naut/buf.h Normal file
View file

@ -0,0 +1,63 @@
/* buf.h — page-aligned, refcounted buffer pool.
*
* This is the spine of the zero-copy data path. One physical buffer is filled
* by recv (io_uring registered buffer), decrypted in place, hashed in place by
* a worker thread, then written to disk via O_DIRECT or sent via SEND_ZC the
* same pages throughout, never memcpy'd.
*
* Ownership model (ABA-free without tagging):
* - naut_buf_get() pops from the freelist and is SINGLE-CONSUMER: only the
* pool's owning reactor thread may call it.
* - naut_buf_put()/naut_buf_ref() are MULTI-PRODUCER: any thread (e.g. a hash
* worker that finished with a buffer) may call them. put() pushes back onto
* the freelist only on the 1->0 refcount transition.
* Because only the owner pops, a buffer in flight is never re-pushed by another
* thread, so the Treiber-stack pop has no ABA hazard.
*/
#ifndef NAUT_BUF_H
#define NAUT_BUF_H
#include "naut/common.h"
typedef struct naut_bufpool naut_bufpool;
typedef struct naut_buf {
_Atomic uint32_t refcnt; /* live references; 0 => on freelist */
uint32_t len; /* bytes of valid payload in data[] */
uint32_t cap; /* == pool block_size */
uint32_t idx; /* index within the pool (for registered bufs)*/
struct naut_buf *fnext; /* freelist link (owner-thread access only) */
naut_bufpool *pool;
uint8_t *data; /* page-aligned, cap bytes */
} naut_buf;
/* block_size must be a multiple of NAUT_PAGE (O_DIRECT alignment).
* If use_hugepages, the data slab is mmap'd with MAP_HUGETLB (falls back to
* normal pages if unavailable). */
naut_bufpool *naut_bufpool_create(uint32_t block_size, uint32_t block_count,
bool use_hugepages);
naut_bufpool *naut_bufpool_create_on_node(uint32_t block_size,
uint32_t block_count,
bool use_hugepages,
int numa_node);
void naut_bufpool_destroy(naut_bufpool *p);
/* Owner thread only. Returns NULL when exhausted (caller applies backpressure).
* Returned buffer has refcnt==1 and len==0. */
naut_buf *naut_buf_get(naut_bufpool *p) NAUT_MUST_USE;
/* Any thread. */
NAUT_INLINE void naut_buf_ref(naut_buf *b) {
atomic_fetch_add_explicit(&b->refcnt, 1, memory_order_relaxed);
}
void naut_buf_put(naut_buf *b);
/* Introspection (approximate under concurrency). */
uint32_t naut_bufpool_capacity(const naut_bufpool *p);
uint32_t naut_bufpool_available(const naut_bufpool *p);
/* Base of the contiguous data slab + total bytes — used to register the whole
* region with io_uring as a single fixed-buffer area. */
void *naut_bufpool_slab(const naut_bufpool *p, size_t *out_bytes);
#endif /* NAUT_BUF_H */

59
include/naut/common.h Normal file
View file

@ -0,0 +1,59 @@
/* common.h — project-wide types, attributes, and error codes.
*
* Pure C11. No allocation, no platform calls; safe to include everywhere.
*/
#ifndef NAUT_COMMON_H
#define NAUT_COMMON_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdatomic.h>
/* --- sizes ------------------------------------------------------------- */
#define NAUT_CACHELINE 64u
#define NAUT_PAGE 4096u
/* BitTorrent wire block size (BEP-3). The fundamental transfer unit. */
#define NAUT_BLOCK (16u * 1024u)
/* --- compiler attributes ----------------------------------------------- */
#define NAUT_LIKELY(x) __builtin_expect(!!(x), 1)
#define NAUT_UNLIKELY(x) __builtin_expect(!!(x), 0)
#define NAUT_INLINE static inline __attribute__((always_inline))
#define NAUT_ALIGNED(n) __attribute__((aligned(n)))
#define NAUT_CACHE_ALIGNED __attribute__((aligned(NAUT_CACHELINE)))
#define NAUT_NORETURN __attribute__((noreturn))
#define NAUT_UNUSED __attribute__((unused))
#define NAUT_PACKED __attribute__((packed))
#define NAUT_MUST_USE __attribute__((warn_unused_result))
#define NAUT_PRINTF(fi, ai) __attribute__((format(printf, fi, ai)))
/* --- small helpers ----------------------------------------------------- */
#define NAUT_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
#define NAUT_MIN(a, b) ((a) < (b) ? (a) : (b))
#define NAUT_MAX(a, b) ((a) > (b) ? (a) : (b))
#define NAUT_ALIGN_UP(x, a) (((uintptr_t)(x) + ((a) - 1)) & ~((uintptr_t)(a) - 1))
#define NAUT_ALIGN_DOWN(x, a) ((uintptr_t)(x) & ~((uintptr_t)(a) - 1))
#define NAUT_IS_POW2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0))
#define NAUT_CONTAINER_OF(ptr, type, member) \
((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - offsetof(type, member)))
/* --- error codes ------------------------------------------------------- */
typedef int naut_err;
enum {
NAUT_OK = 0,
NAUT_ERR_NOMEM = -1,
NAUT_ERR_INVAL = -2,
NAUT_ERR_IO = -3,
NAUT_ERR_AGAIN = -4, /* would block / retry */
NAUT_ERR_PROTO = -5, /* protocol violation */
NAUT_ERR_RANGE = -6,
NAUT_ERR_NOSYS = -7, /* unsupported by kernel/build */
NAUT_ERR_FULL = -8,
NAUT_ERR_EMPTY = -9,
NAUT_ERR_NOTFOUND = -10,
};
const char *naut_strerror(naut_err e);
#endif /* NAUT_COMMON_H */

66
include/naut/dht.h Normal file
View file

@ -0,0 +1,66 @@
/* dht.h - BEP-5 KRPC codec and bounded IPv4 get_peers traversal. */
#ifndef NAUT_DHT_H
#define NAUT_DHT_H
#include "naut/common.h"
#include "naut/tracker.h"
#define NAUT_DHT_ID_LEN 20
#define NAUT_DHT_MAX_NODES 256
#define NAUT_DHT_MAX_PEERS 256
typedef struct {
uint8_t id[NAUT_DHT_ID_LEN];
uint8_t ip[4];
uint16_t port;
} naut_dht_node;
typedef enum {
NAUT_DHT_RESPONSE,
NAUT_DHT_ERROR
} naut_dht_message_type;
typedef struct {
naut_dht_message_type type;
uint8_t transaction[8];
size_t transaction_len;
uint8_t id[NAUT_DHT_ID_LEN];
bool has_id;
uint8_t token[64];
size_t token_len;
naut_dht_node *nodes;
size_t num_nodes;
naut_peer_addr *peers;
size_t num_peers;
int error_code;
} naut_dht_response;
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);
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);
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);
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);
naut_err naut_dht_parse_response(const uint8_t *data, size_t len,
naut_dht_response *out);
void naut_dht_response_free(naut_dht_response *response);
/* Query bootstrap endpoints ("host:port") and iteratively follow returned
* compact nodes until peers are found or the bounded traversal is exhausted. */
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);
#endif /* NAUT_DHT_H */

42
include/naut/event.h Normal file
View file

@ -0,0 +1,42 @@
/* event.h - control-plane event bus, never called from byte-processing workers. */
#ifndef NAUT_EVENT_H
#define NAUT_EVENT_H
#include "naut/common.h"
typedef enum {
NAUT_EVENT_TORRENT_ADDED,
NAUT_EVENT_PIECE_COMPLETE,
NAUT_EVENT_FILE_COMPLETE,
NAUT_EVENT_TORRENT_FINISHED,
NAUT_EVENT_PEER_CONNECTED,
NAUT_EVENT_ALERT,
} naut_event_type;
typedef struct {
naut_event_type type;
uint64_t torrent_id;
uint32_t index;
const char *message;
const char *path;
} naut_event;
typedef void (*naut_event_cb)(void *context, const naut_event *event);
typedef struct naut_event_bus naut_event_bus;
naut_event_bus *naut_event_bus_create(void);
void naut_event_bus_destroy(naut_event_bus *bus);
/* Subscribe/unsubscribe are control-thread operations. emit snapshots the
* subscriber list, allowing callbacks to register work without holding a bus
* lock. */
naut_err naut_event_subscribe(naut_event_bus *bus, naut_event_cb callback,
void *context, uint64_t *subscription_id);
void naut_event_unsubscribe(naut_event_bus *bus, uint64_t subscription_id);
void naut_event_emit(naut_event_bus *bus, const naut_event *event);
const char *naut_event_type_name(naut_event_type type);
bool naut_event_type_parse(const char *name, naut_event_type *type);
#endif /* NAUT_EVENT_H */

70
include/naut/extension.h Normal file
View file

@ -0,0 +1,70 @@
/* extension.h - BEP-10 transport, BEP-9 metadata, and BEP-11 PEX codecs. */
#ifndef NAUT_EXTENSION_H
#define NAUT_EXTENSION_H
#include "naut/common.h"
#include "naut/tracker.h"
#define NAUT_EXT_UT_METADATA 1
#define NAUT_EXT_UT_PEX 2
#define NAUT_METADATA_BLOCK (16u * 1024u)
#define NAUT_METADATA_MAX (4u * 1024u * 1024u)
typedef struct {
uint8_t ut_metadata;
uint8_t ut_pex;
uint32_t metadata_size;
uint32_t reqq;
uint16_t port;
} naut_ext_handshake;
/* Build a complete peer-wire extended message (length, message ID 20,
* extension ID, payload). The returned buffer is malloc-owned. */
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_err naut_ext_parse_handshake(const uint8_t *payload, size_t len,
naut_ext_handshake *out);
typedef enum {
NAUT_METADATA_REQUEST = 0,
NAUT_METADATA_DATA = 1,
NAUT_METADATA_REJECT = 2
} naut_metadata_type;
typedef struct {
naut_metadata_type type;
uint32_t piece;
uint32_t total_size;
const uint8_t *data;
size_t data_len;
} naut_metadata_msg;
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);
naut_err naut_metadata_parse(const uint8_t *payload, size_t len,
naut_metadata_msg *out);
typedef struct {
naut_peer_addr *added;
uint8_t *added_flags;
size_t num_added;
naut_peer_addr *dropped;
size_t num_dropped;
} naut_pex_msg;
naut_err naut_pex_build(uint8_t ext_id, const naut_pex_msg *msg,
uint8_t **out, size_t *out_len);
naut_err naut_pex_parse(const uint8_t *payload, size_t len, naut_pex_msg *out);
void naut_pex_free(naut_pex_msg *msg);
/* Fetch and SHA-1 verify a v1 torrent's raw info dictionary from one peer.
* The returned buffer is malloc-owned. */
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);
#endif /* NAUT_EXTENSION_H */

45
include/naut/hash.h Normal file
View file

@ -0,0 +1,45 @@
/* hash.h — SHA-1 and SHA-256, the throughput-critical primitives.
*
* SHA-1 backs BitTorrent v1 (piece hashes + info-hash). SHA-256 backs v2
* (Merkle leaves + info-hash). SHA-256 has a runtime-dispatched SHA-NI path so
* a single core verifies well above the 1.25 GB/s the 10 GbE target demands;
* the portable scalar path is the fallback and the cross-check oracle.
*/
#ifndef NAUT_HASH_H
#define NAUT_HASH_H
#include "naut/common.h"
#define NAUT_SHA1_LEN 20
#define NAUT_SHA256_LEN 32
/* --- SHA-1 ------------------------------------------------------------- */
typedef struct {
uint32_t h[5];
uint64_t len; /* total bytes hashed */
uint8_t block[64];
size_t used;
} naut_sha1_ctx;
void naut_sha1_init(naut_sha1_ctx *c);
void naut_sha1_update(naut_sha1_ctx *c, const void *data, size_t len);
void naut_sha1_final(naut_sha1_ctx *c, uint8_t out[NAUT_SHA1_LEN]);
void naut_sha1(const void *data, size_t len, uint8_t out[NAUT_SHA1_LEN]);
/* --- SHA-256 ----------------------------------------------------------- */
typedef struct {
uint32_t h[8];
uint64_t len;
uint8_t block[64];
size_t used;
} naut_sha256_ctx;
void naut_sha256_init(naut_sha256_ctx *c);
void naut_sha256_update(naut_sha256_ctx *c, const void *data, size_t len);
void naut_sha256_final(naut_sha256_ctx *c, uint8_t out[NAUT_SHA256_LEN]);
void naut_sha256(const void *data, size_t len, uint8_t out[NAUT_SHA256_LEN]);
/* Which SHA-256 backend was selected at startup ("sha-ni" or "scalar"). */
const char *naut_sha256_backend(void);
#endif /* NAUT_HASH_H */

47
include/naut/list.h Normal file
View file

@ -0,0 +1,47 @@
/* list.h — intrusive circular doubly-linked list (header-only).
*
* Zero allocation: the node lives inside your struct. Recover the owner with
* NAUT_CONTAINER_OF. This is the workhorse list for peer sets, freelists of
* objects, timer wheels, etc.
*/
#ifndef NAUT_LIST_H
#define NAUT_LIST_H
#include "naut/common.h"
typedef struct naut_list {
struct naut_list *prev;
struct naut_list *next;
} naut_list;
NAUT_INLINE void naut_list_init(naut_list *l) { l->prev = l; l->next = l; }
NAUT_INLINE bool naut_list_empty(const naut_list *l) { return l->next == l; }
NAUT_INLINE void naut__link(naut_list *n, naut_list *p, naut_list *x) {
n->prev = p; n->next = x; p->next = n; x->prev = n;
}
/* insert n at head / tail of list l */
NAUT_INLINE void naut_list_push_front(naut_list *l, naut_list *n) { naut__link(n, l, l->next); }
NAUT_INLINE void naut_list_push_back(naut_list *l, naut_list *n) { naut__link(n, l->prev, l); }
NAUT_INLINE void naut_list_del(naut_list *n) {
n->prev->next = n->next;
n->next->prev = n->prev;
n->prev = n->next = n; /* safe to del again / detect detached */
}
NAUT_INLINE naut_list *naut_list_front(const naut_list *l) { return l->next; }
NAUT_INLINE naut_list *naut_list_back(const naut_list *l) { return l->prev; }
#define naut_list_entry(ptr, type, member) NAUT_CONTAINER_OF(ptr, type, member)
#define naut_list_for_each(it, l) \
for ((it) = (l)->next; (it) != (l); (it) = (it)->next)
/* safe against deletion of the current node */
#define naut_list_for_each_safe(it, tmp, l) \
for ((it) = (l)->next, (tmp) = (it)->next; \
(it) != (l); (it) = (tmp), (tmp) = (it)->next)
#endif /* NAUT_LIST_H */

45
include/naut/log.h Normal file
View file

@ -0,0 +1,45 @@
/* log.h — leveled logging.
*
* Phase 1: a straightforward thread-safe stderr logger (one writev per record,
* so lines never interleave). The data path does not log per-message; this is
* for lifecycle, errors, and stats. A lock-free per-thread ring drain is a
* later optimization behind the same macros, so call sites never change.
*/
#ifndef NAUT_LOG_H
#define NAUT_LOG_H
#include "naut/common.h"
typedef enum {
NAUT_LOG_ERROR = 0,
NAUT_LOG_WARN,
NAUT_LOG_INFO,
NAUT_LOG_DEBUG,
NAUT_LOG_TRACE,
} naut_log_level;
void naut_log_set_level(naut_log_level lvl);
naut_log_level naut_log_get_level(void);
void naut_log_emit(naut_log_level lvl, const char *file, int line,
const char *fmt, ...) NAUT_PRINTF(4, 5);
#define NAUT_LOG(lvl, ...) \
do { if ((lvl) <= naut_log_get_level()) \
naut_log_emit((lvl), __FILE__, __LINE__, __VA_ARGS__); } while (0)
#define NAUT_ERROR(...) NAUT_LOG(NAUT_LOG_ERROR, __VA_ARGS__)
#define NAUT_WARN(...) NAUT_LOG(NAUT_LOG_WARN, __VA_ARGS__)
#define NAUT_INFO(...) NAUT_LOG(NAUT_LOG_INFO, __VA_ARGS__)
#define NAUT_DEBUG(...) NAUT_LOG(NAUT_LOG_DEBUG, __VA_ARGS__)
#define NAUT_TRACE(...) NAUT_LOG(NAUT_LOG_TRACE, __VA_ARGS__)
/* Fatal: log and abort(). Use only for unrecoverable invariant violations. */
NAUT_NORETURN void naut_panic(const char *file, int line, const char *fmt, ...)
NAUT_PRINTF(3, 4);
#define NAUT_PANIC(...) naut_panic(__FILE__, __LINE__, __VA_ARGS__)
#define NAUT_ASSERT(cond) \
do { if (NAUT_UNLIKELY(!(cond))) NAUT_PANIC("assertion failed: %s", #cond); } while (0)
#endif /* NAUT_LOG_H */

40
include/naut/merkle.h Normal file
View file

@ -0,0 +1,40 @@
/* merkle.h — BitTorrent v2 (BEP-52) SHA-256 Merkle trees.
*
* In v2 each file is split into 16 KiB leaf blocks; the leaf hashes form a
* binary Merkle tree whose interior nodes are SHA-256(left || right). When the
* leaf count is not a power of two, the tree is padded with *zero hashes* (a
* block of 32 zero bytes at the leaf level, then SHA-256 of two children up the
* tree) so the shape is a perfect binary tree. The root at the "piece layer"
* boundary gives per-piece verifiability; the whole-file root goes in the
* metainfo file tree.
*
* This module provides the tree primitive; metainfo/storage wire it to files.
*/
#ifndef NAUT_MERKLE_H
#define NAUT_MERKLE_H
#include "naut/common.h"
#include "naut/hash.h"
#define NAUT_MERKLE_LEAF (16u * 1024u) /* BEP-52 block size */
/* Compute the Merkle root of `nleaves` 32-byte leaf hashes, padding up to the
* next power of two with zero hashes. nleaves==0 yields the all-zero hash.
* `leaves` is nleaves*32 bytes; out is 32 bytes. Scratch is allocated
* internally. Returns NAUT_OK or NAUT_ERR_NOMEM. */
naut_err naut_merkle_root(const uint8_t *leaves, size_t nleaves,
uint8_t out[NAUT_SHA256_LEN]);
/* As above but pad to a fixed `block_count` (>= nleaves, power of two) rather
* than the next power of two used to compute a piece-layer root where the
* tree height is fixed by the piece size. */
naut_err naut_merkle_root_padded(const uint8_t *leaves, size_t nleaves,
size_t block_count,
uint8_t out[NAUT_SHA256_LEN]);
/* Hash a contiguous data buffer into leaf hashes (one SHA-256 per 16 KiB, last
* leaf may be short). out must hold ceil(len/16KiB)*32 bytes. Returns the leaf
* count. */
size_t naut_merkle_leaves(const uint8_t *data, size_t len, uint8_t *out);
#endif /* NAUT_MERKLE_H */

68
include/naut/metainfo.h Normal file
View file

@ -0,0 +1,68 @@
/* metainfo.h — .torrent and magnet parsing (v1 / v2 / hybrid).
*
* The info-hash is computed over the *raw* bytes of the `info` dictionary as
* they appear in the file (bencode preserves that span), never by re-encoding:
* v1 info-hash = SHA-1 (info-bytes)
* v2 info-hash = SHA-256(info-bytes)
* A hybrid torrent carries both and so joins both the v1 and v2 swarms.
*
* Phase 2 parses v1 fully (name, piece length, file list, the 20-byte piece
* hash table) and computes the v2 info-hash + version for hybrid/v2 files; the
* full v2 file-tree / piece-layer plumbing is wired in Phase 3 (storage).
*/
#ifndef NAUT_METAINFO_H
#define NAUT_METAINFO_H
#include "naut/common.h"
#include "naut/hash.h"
typedef struct {
char *path; /* '/'-joined, NUL-terminated */
int64_t length;
} naut_file;
typedef struct naut_metainfo {
bool has_v1, has_v2;
uint8_t infohash_v1[NAUT_SHA1_LEN];
uint8_t infohash_v2[NAUT_SHA256_LEN];
char *name;
int64_t piece_length;
int64_t total_length;
/* v1 piece hash table: num_pieces * 20 bytes (owned copy) */
uint32_t num_pieces;
const uint8_t *piece_hashes;
naut_file *files; size_t num_files;
char **trackers; size_t num_trackers; /* announce + announce-list, flattened */
/* internals kept alive so piece_hashes/name stay valid */
void *_owned;
} naut_metainfo;
naut_err naut_metainfo_parse(const uint8_t *data, size_t len, naut_metainfo *out);
/* Parse a raw bencoded info dictionary obtained through BEP-9. Optional
* tracker URLs are copied into the resulting metainfo. */
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);
void naut_metainfo_free(naut_metainfo *mi);
/* hex of the v1 info-hash (41 bytes incl NUL) — for logging/RPC. */
void naut_infohash_v1_hex(const naut_metainfo *mi, char out[41]);
/* --- magnet links -------------------------------------------------------- */
typedef struct {
bool has_v1, has_v2;
uint8_t infohash_v1[NAUT_SHA1_LEN];
uint8_t infohash_v2[NAUT_SHA256_LEN];
char *name; /* dn (display name), may be NULL */
char **trackers; size_t num_trackers; /* tr= params */
} naut_magnet;
naut_err naut_magnet_parse(const char *uri, naut_magnet *out);
void naut_magnet_free(naut_magnet *m);
#endif /* NAUT_METAINFO_H */

34
include/naut/mpmc.h Normal file
View file

@ -0,0 +1,34 @@
/* mpmc.h — bounded lock-free queue of pointers (Dmitry Vyukov's algorithm).
*
* One implementation covers every cross-thread hand-off in the engine:
* - control-plane -> reactor command queues (MPSC usage)
* - reactor -> hash worker pool job queue (MPMC usage)
* - worker -> reactor completion queue (MPSC usage)
* Wait-free in the common case, no per-op allocation. Capacity is fixed at
* init and must be a power of two.
*/
#ifndef NAUT_MPMC_H
#define NAUT_MPMC_H
#include "naut/common.h"
typedef struct naut_mpmc_cell {
_Atomic size_t seq;
void *data;
} naut_mpmc_cell;
typedef struct naut_mpmc {
NAUT_CACHE_ALIGNED naut_mpmc_cell *buffer;
size_t mask;
NAUT_CACHE_ALIGNED _Atomic size_t enqueue_pos;
NAUT_CACHE_ALIGNED _Atomic size_t dequeue_pos;
} naut_mpmc;
naut_err naut_mpmc_init(naut_mpmc *q, size_t capacity_pow2);
void naut_mpmc_destroy(naut_mpmc *q);
/* Both return false without blocking when full/empty respectively. */
bool naut_mpmc_push(naut_mpmc *q, void *p);
bool naut_mpmc_pop(naut_mpmc *q, void **out);
#endif /* NAUT_MPMC_H */

88
include/naut/mse.h Normal file
View file

@ -0,0 +1,88 @@
/* mse.h - BitTorrent Message Stream Encryption (MSE/PE) transport. */
#ifndef NAUT_MSE_H
#define NAUT_MSE_H
#include "naut/common.h"
#include "naut/peer.h"
#include "naut/rc4.h"
#include <sys/types.h>
#define NAUT_MSE_DH_LEN 96
typedef struct {
naut_rc4 send;
naut_rc4 recv;
bool active;
} naut_mse_stream;
/* ---- sans-IO handshake state machine ------------------------------------- *
* The outgoing MSE/PE handshake as a pure state machine over byte buffers no
* sockets so the same logic drives the blocking apps and the io_uring reactor
* (where blocking in a handshake would stall a whole core's worth of peers).
*
* Drive it like a codec: pump NEED_WRITE bytes out, feed NEED_READ bytes in,
* repeat until DONE or ERROR, then call _finish().
*
* h = naut_mse_handshake_begin(info_hash, peer_id, reserved);
* for (;;) switch (naut_mse_handshake_status(h)) {
* case NAUT_MSE_HS_NEED_WRITE: pull bytes, write them to the peer; break;
* case NAUT_MSE_HS_NEED_READ: read bytes from the peer, feed them; break;
* case NAUT_MSE_HS_DONE: naut_mse_handshake_finish(h, ...); goto ok;
* case NAUT_MSE_HS_ERROR: ... ; goto err;
* }
*/
typedef enum {
NAUT_MSE_HS_NEED_READ,
NAUT_MSE_HS_NEED_WRITE,
NAUT_MSE_HS_DONE,
NAUT_MSE_HS_ERROR,
} naut_mse_hs_status;
typedef struct naut_mse_handshake naut_mse_handshake;
naut_mse_handshake *naut_mse_handshake_begin(
const uint8_t info_hash[20],
const uint8_t peer_id[NAUT_PEERID_LEN],
uint64_t reserved);
void naut_mse_handshake_free(naut_mse_handshake *h);
naut_mse_hs_status naut_mse_handshake_status(const naut_mse_handshake *h);
/* Copy pending outgoing bytes into buf (up to cap); returns the count, 0 when
* nothing is queued. Call repeatedly until it returns 0. */
size_t naut_mse_handshake_pull(naut_mse_handshake *h, uint8_t *buf, size_t cap);
/* Feed received bytes; *consumed reports how many were absorbed (the rest, if
* any, must be re-fed after DONE that remainder is the start of the encrypted
* payload stream). Returns the new status. */
naut_mse_hs_status naut_mse_handshake_feed(naut_mse_handshake *h,
const uint8_t *data, size_t len,
size_t *consumed);
/* Valid once status is DONE: hand out the negotiated stream and the peer's
* decrypted BitTorrent handshake. */
naut_err naut_mse_handshake_finish(naut_mse_handshake *h,
naut_mse_stream *stream,
uint8_t remote_handshake[NAUT_HANDSHAKE_LEN]);
/* Blocking convenience wrapper over the state machine: perform the whole
* outgoing handshake on a blocking socket, offering RC4 only. The BitTorrent
* handshake is carried as IA; the peer's decrypted handshake is returned in
* remote_handshake. */
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]);
/* Stream I/O after a successful handshake. Encryption/decryption is in-place
* with connection-owned RC4 state. send_all preserves the caller's buffer. */
bool naut_mse_send_all(int fd, naut_mse_stream *stream,
const void *data, size_t len);
ssize_t naut_mse_recv(int fd, naut_mse_stream *stream,
void *data, size_t len);
#endif /* NAUT_MSE_H */

View file

@ -0,0 +1,49 @@
/* naut_plugin.h - stable versioned native plugin ABI. */
#ifndef NAUT_PLUGIN_H
#define NAUT_PLUGIN_H
#include "naut/common.h"
#include "naut/event.h"
#define NAUT_PLUGIN_ABI_VERSION 1u
typedef struct {
uint32_t abi_version;
uint32_t struct_size;
const char *name;
void *(*open)(const char *root, naut_err *error);
void (*close)(void *storage);
naut_err (*read)(void *storage, int64_t offset, void *buffer, size_t length);
naut_err (*write)(void *storage, int64_t offset,
const void *buffer, size_t length);
} naut_storage_backend_v1;
/* request_json is a JSON object or null. response_json must be malloc-owned
* compact JSON on success; the host frees it after parsing. */
typedef naut_err (*naut_plugin_rpc_fn)(void *context,
const char *request_json,
char **response_json);
typedef void (*naut_plugin_event_fn)(void *context,
const naut_event *event);
typedef struct naut_host_api {
uint32_t abi_version;
uint32_t struct_size;
void *host_context;
naut_err (*set_plugin_name)(void *host_context, const char *name);
naut_err (*register_rpc)(void *host_context, const char *method,
naut_plugin_rpc_fn callback, void *context);
naut_err (*register_storage_backend)(
void *host_context, const naut_storage_backend_v1 *backend);
naut_err (*subscribe_event)(void *host_context,
naut_plugin_event_fn callback,
void *context);
void (*emit_event)(void *host_context, const naut_event *event);
void (*log)(void *host_context, int level, const char *message);
} naut_host_api;
/* Every plugin exports this exact symbol. */
typedef naut_err (*naut_plugin_register_fn)(const naut_host_api *host);
#endif /* NAUT_PLUGIN_H */

26
include/naut/net.h Normal file
View file

@ -0,0 +1,26 @@
/* net.h — socket setup (the kernel-facing seam, part 1).
*
* All socket option choices that matter for 10 GbE live here: SO_REUSEPORT so
* each reactor owns a listen queue and the kernel shards inbound peers across
* cores, TCP_NODELAY (BitTorrent is latency-sensitive on small control msgs),
* and large socket buffers so the BDP fits.
*/
#ifndef NAUT_NET_H
#define NAUT_NET_H
#include "naut/common.h"
#include <netinet/in.h>
/* Create a TCP listener bound to `port`. With reuseport=true, multiple reactors
* may each create one on the same port and the kernel load-balances accepts. */
int naut_net_listen(uint16_t port, int backlog, bool reuseport);
/* Tune an accepted/connected peer socket for throughput. */
void naut_net_tune_peer(int fd);
/* Set send/recv socket buffer sizes (bytes); 0 leaves the kernel default. */
void naut_net_set_bufsizes(int fd, int sndbuf, int rcvbuf);
int naut_net_set_nonblock(int fd, bool on);
#endif /* NAUT_NET_H */

72
include/naut/peer.h Normal file
View file

@ -0,0 +1,72 @@
/* peer.h — BitTorrent peer wire protocol (BEP-3), sans-IO.
*
* This is a pure codec: it never touches a socket. You feed it bytes and it
* yields parsed messages; you ask it to build a message and it writes bytes.
* That keeps the protocol unit-testable in isolation and lets the SAME codec be
* driven by the simple blocking leecher (Phase 3) and by the io_uring reactor
* (Phase 6) without change the I/O strategy is somebody else's problem.
*/
#ifndef NAUT_PEER_H
#define NAUT_PEER_H
#include "naut/common.h"
#define NAUT_HANDSHAKE_LEN 68
#define NAUT_PEERID_LEN 20
/* Reject absurd length prefixes early: largest legitimate message is a PIECE,
* 9 + block. Allow generous slack over the 16 KiB default block. */
#define NAUT_MSG_MAX (1u << 20)
typedef enum {
NAUT_MSG_CHOKE = 0,
NAUT_MSG_UNCHOKE = 1,
NAUT_MSG_INTERESTED = 2,
NAUT_MSG_NOT_INTERESTED = 3,
NAUT_MSG_HAVE = 4,
NAUT_MSG_BITFIELD = 5,
NAUT_MSG_REQUEST = 6,
NAUT_MSG_PIECE = 7,
NAUT_MSG_CANCEL = 8,
NAUT_MSG_PORT = 9,
NAUT_MSG_EXTENDED = 20, /* BEP-10 extension payload */
NAUT_MSG_KEEPALIVE = 255, /* synthetic: length-prefix of 0 */
} naut_msg_type;
typedef struct {
naut_msg_type type;
uint32_t index; /* HAVE/REQUEST/PIECE/CANCEL */
uint32_t begin; /* REQUEST/PIECE/CANCEL */
uint32_t length; /* REQUEST/CANCEL; PIECE: block length */
const uint8_t *payload; /* PIECE: block bytes; BITFIELD: bytes */
size_t payload_len;
} naut_msg;
/* --- handshake ----------------------------------------------------------- */
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);
/* Returns true on a well-formed handshake with the expected protocol string. */
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);
/* --- decode -------------------------------------------------------------- */
/* Parse one message from buf[0..len). Returns bytes consumed (>0) with *out
* filled (payload pointers alias into buf), 0 if more bytes are needed, or
* NAUT_ERR_PROTO (<0) on a malformed/oversized frame. */
int naut_peer_msg_parse(const uint8_t *buf, size_t len, naut_msg *out);
/* --- encode (all return bytes written) ----------------------------------- */
size_t naut_peer_keepalive(uint8_t out[4]);
size_t naut_peer_msg_simple(uint8_t out[5], naut_msg_type t); /* choke..not_interested */
size_t naut_peer_msg_have(uint8_t out[9], uint32_t index);
size_t naut_peer_msg_request(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length);
size_t naut_peer_msg_cancel(uint8_t out[17], uint32_t index, uint32_t begin, uint32_t length);
/* PIECE header (13 bytes); the block bytes follow separately on the wire. */
size_t naut_peer_msg_piece_header(uint8_t out[13], uint32_t index, uint32_t begin, uint32_t block_len);
/* BITFIELD into out (must hold 5 + nbytes); returns total length. */
size_t naut_peer_msg_bitfield(uint8_t *out, const uint8_t *bf, size_t nbytes);
#endif /* NAUT_PEER_H */

86
include/naut/piece.h Normal file
View file

@ -0,0 +1,86 @@
/* piece.h — download state: block requests, piece assembly, verify, persist.
*
* A piece is assembled in memory as its blocks arrive, verified against the
* metainfo hash (SHA-1 for v1/hybrid), then written to storage in one shot
* so a corrupt piece never reaches disk.
*/
#ifndef NAUT_PIECE_H
#define NAUT_PIECE_H
#include "naut/common.h"
#include "naut/metainfo.h"
#include "naut/storage.h"
#include "naut/bitfield.h"
#include "naut/worker.h"
typedef struct naut_download naut_download;
naut_download *naut_download_create(const naut_metainfo *mi, naut_storage *st);
void naut_download_destroy(naut_download *d);
/* Optional hash offload. Completed-piece SHA-1 jobs run on the worker pool;
* naut_download_poll() finalizes verified pieces on the owning engine thread.
* The pool must outlive the download. */
void naut_download_set_worker_pool(naut_download *d, naut_worker_pool *pool);
naut_err naut_download_poll(naut_download *d, uint32_t *pieces_completed);
/* ---- multi-peer swarm interface (Phase 4) ------------------------------- *
* Availability: report what each peer has so rarest-first can rank pieces. A
* peer's bitfield is added on connect (BITFIELD) and removed on disconnect; a
* single HAVE bumps one piece. */
void naut_download_add_bitfield(naut_download *d, const naut_bitfield *peer_have);
void naut_download_remove_bitfield(naut_download *d, const naut_bitfield *peer_have);
void naut_download_inc_avail(naut_download *d, uint32_t piece);
/* Pick the next block to request for a peer with `peer_have`. Uses rarest-first,
* prefers finishing in-progress pieces, and switches to endgame (allowing a
* block to be requested from multiple peers) when few blocks remain. Returns
* false if this peer has nothing useful to request right now. */
bool naut_download_pick(naut_download *d, const naut_bitfield *peer_have,
uint32_t *index, uint32_t *begin, uint32_t *length);
/* Endgame may duplicate a block across peers, but never back to the same peer.
* `peer_has_request` lets the caller expose that peer's current request set.
* Outside endgame this behaves exactly like naut_download_pick(). */
typedef bool (*naut_request_active_cb)(void *ctx, uint32_t index, uint32_t begin);
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);
/* Release a request (peer disconnected, or cancel) so the block can be re-picked. */
void naut_download_unrequest(naut_download *d, uint32_t index, uint32_t begin);
bool naut_download_have(const naut_download *d, uint32_t piece);
bool naut_download_in_endgame(const naut_download *d);
/* Per-file completion: fired the moment the last piece overlapping a file's byte
* range verifies (so the file's bytes on disk are final and it is safe to move).
* This is the engine seam for the user's "move files as they finish" feature
* the scripting layer (Phase 7) forwards this to an on_file_complete hook and may
* then call naut_storage_relocate(). The callback runs on the engine thread; a
* script must marshal any action back through the command queue.
*
* NOTE: fires during naut_download_on_block(); a single block may complete
* several files (small files packed into one piece). Empty files are reported as
* complete via naut_download_file_complete() but do not fire the callback. */
typedef void (*naut_file_complete_cb)(void *ctx, uint32_t file_index, const char *path);
void naut_download_set_file_cb(naut_download *d, naut_file_complete_cb cb, void *ctx);
bool naut_download_file_complete(const naut_download *d, uint32_t file_index);
/* Hand out the next block to request. false => nothing left to hand out right
* now (all blocks have been requested). */
bool naut_download_next_request(naut_download *d,
uint32_t *index, uint32_t *begin, uint32_t *length);
/* Feed a received PIECE block. *piece_done is set true iff this block completed
* a piece that then verified and was written to storage. With a worker pool,
* completion is reported later through naut_download_poll(). */
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);
bool naut_download_complete(const naut_download *d);
uint32_t naut_download_num_pieces(const naut_download *d);
uint32_t naut_download_pieces_done(const naut_download *d);
uint64_t naut_download_bytes_done(const naut_download *d);
#endif /* NAUT_PIECE_H */

29
include/naut/pipeline.h Normal file
View file

@ -0,0 +1,29 @@
/* pipeline.h - adaptive request window based on observed bandwidth-delay product. */
#ifndef NAUT_PIPELINE_H
#define NAUT_PIPELINE_H
#include "naut/common.h"
typedef struct {
double rtt_seconds;
double bytes_per_second;
double last_sample_at;
uint32_t depth;
uint32_t min_depth;
uint32_t max_depth;
uint32_t block_size;
} naut_pipeline;
void naut_pipeline_init(naut_pipeline *p, uint32_t block_size,
uint32_t min_depth, uint32_t max_depth,
uint32_t initial_depth);
/* Record one completed request. sent_at and received_at are monotonic seconds.
* The controller smooths RTT and delivery rate, then targets 2x BDP to absorb
* scheduling jitter without allowing an unbounded request window. */
void naut_pipeline_on_block(naut_pipeline *p, uint32_t bytes,
double sent_at, double received_at);
uint32_t naut_pipeline_depth(const naut_pipeline *p);
#endif /* NAUT_PIPELINE_H */

25
include/naut/plugin.h Normal file
View file

@ -0,0 +1,25 @@
/* plugin.h - native plugin loader and registered backend inventory. */
#ifndef NAUT_PLUGIN_HOST_H
#define NAUT_PLUGIN_HOST_H
#include "naut/event.h"
#include "naut/naut_plugin.h"
#include "naut/rpc.h"
typedef struct naut_plugin_manager naut_plugin_manager;
naut_plugin_manager *naut_plugin_manager_create(
naut_rpc_registry *rpc, naut_event_bus *events);
void naut_plugin_manager_destroy(naut_plugin_manager *manager);
naut_err naut_plugin_load(naut_plugin_manager *manager, const char *path);
size_t naut_plugin_count(const naut_plugin_manager *manager);
const char *naut_plugin_name(const naut_plugin_manager *manager, size_t index);
size_t naut_plugin_storage_count(const naut_plugin_manager *manager);
const char *naut_plugin_storage_name(const naut_plugin_manager *manager,
size_t index);
const naut_storage_backend_v1 *naut_plugin_storage_backend(
const naut_plugin_manager *manager, const char *name);
#endif /* NAUT_PLUGIN_HOST_H */

21
include/naut/rc4.h Normal file
View file

@ -0,0 +1,21 @@
/* rc4.h — ARC4 stream cipher for MSE/PE (BitTorrent message-stream encryption).
*
* MSE keys RC4 from a SHA-1 of the negotiated DH secret and *discards the first
* 1024 keystream bytes* before use (the well-known RC4 keystream-bias defense),
* so naut_rc4_init takes a drop count. This is obfuscation, not strong crypto
* its job is firewall/ISP evasion, and the engineering concern here is that it
* costs real CPU at 10 GbE (see the throughput budget), hence it lives behind a
* tight, in-place API.
*/
#ifndef NAUT_RC4_H
#define NAUT_RC4_H
#include "naut/common.h"
typedef struct { uint8_t s[256]; uint8_t i, j; } naut_rc4;
void naut_rc4_init(naut_rc4 *c, const void *key, size_t keylen, size_t drop);
/* XOR keystream into buf in place (encrypt == decrypt). */
void naut_rc4_xor(naut_rc4 *c, void *buf, size_t len);
#endif /* NAUT_RC4_H */

45
include/naut/rpc.h Normal file
View file

@ -0,0 +1,45 @@
/* rpc.h - versioned length-prefixed control protocol and command registry. */
#ifndef NAUT_RPC_H
#define NAUT_RPC_H
#include "naut/common.h"
#include "naut/event.h"
#include <jansson.h>
#define NAUT_RPC_VERSION 1
#define NAUT_RPC_MAX_PAYLOAD (1u << 20)
typedef enum {
NAUT_RPC_REQUEST = 1,
NAUT_RPC_RESPONSE = 2,
NAUT_RPC_EVENT = 3,
} naut_rpc_frame_type;
typedef struct naut_rpc_registry naut_rpc_registry;
typedef json_t *(*naut_rpc_handler)(void *context, const json_t *params,
naut_err *error);
naut_rpc_registry *naut_rpc_registry_create(void);
void naut_rpc_registry_destroy(naut_rpc_registry *registry);
naut_err naut_rpc_register(naut_rpc_registry *registry, const char *method,
naut_rpc_handler handler, void *context);
void naut_rpc_unregister(naut_rpc_registry *registry, const char *method);
json_t *naut_rpc_dispatch(naut_rpc_registry *registry, const char *method,
const json_t *params, naut_err *error);
naut_err naut_rpc_send_json(int fd, naut_rpc_frame_type type,
const json_t *payload);
naut_err naut_rpc_recv_json(int fd, naut_rpc_frame_type *type,
json_t **payload);
int naut_rpc_connect_unix(const char *socket_path);
/* Connect to a UNIX socket and perform one request/response exchange. */
naut_err naut_rpc_call(const char *socket_path, const char *method,
const json_t *params, json_t **response);
/* Convert an event into the stable JSON event representation. */
json_t *naut_rpc_event_json(const naut_event *event);
#endif /* NAUT_RPC_H */

36
include/naut/script.h Normal file
View file

@ -0,0 +1,36 @@
/* script.h - sandboxed Lua event hooks on a dedicated bounded worker. */
#ifndef NAUT_SCRIPT_H
#define NAUT_SCRIPT_H
#include "naut/event.h"
typedef struct naut_script naut_script;
typedef naut_err (*naut_script_move_file_cb)(void *context,
uint64_t torrent_id,
uint32_t file_index,
const char *destination);
typedef struct {
uint64_t queued;
uint64_t handled;
uint64_t dropped;
uint64_t errors;
uint64_t move_requests;
} naut_script_stats;
/* script_path is loaded before the worker starts. queue_capacity bounds copied
* events and must be non-zero. The VM owns no filesystem or process APIs. */
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);
void naut_script_destroy(naut_script *script);
void naut_script_get_stats(const naut_script *script,
naut_script_stats *stats);
const char *naut_script_last_error(naut_script *script);
#endif /* NAUT_SCRIPT_H */

41
include/naut/session.h Normal file
View file

@ -0,0 +1,41 @@
/* session.h — minimal torrent registry for the control plane.
*
* The control plane (RPC / plugins / scripts) refers to torrents by a stable
* uint64 id; the engine refers to them by their storage. This registry is the
* single map between the two, so a script-driven command like move_file can be
* resolved to a concrete naut_storage and acted on.
*
* Threading: the registry is *owner-thread confined*. In nautd every call
* (add/remove/move, all from RPC handlers and the move-drain) runs on the
* daemon's main thread, so no internal locking is needed. Commands originating
* on other threads (e.g. a script's naut.move_file) must be marshalled onto the
* owner thread first which is exactly what nautd's bounded move queue does.
*/
#ifndef NAUT_SESSION_H
#define NAUT_SESSION_H
#include "naut/common.h"
#include "naut/storage.h"
typedef struct naut_session naut_session;
naut_session *naut_session_create(void);
/* Closes every storage still registered. */
void naut_session_destroy(naut_session *s);
/* Register `storage` under `id`; the session takes ownership and will close it
* on remove/destroy. Fails with NAUT_ERR_INVAL if `id` is already present. */
naut_err naut_session_add(naut_session *s, uint64_t id, naut_storage *storage);
/* Close and forget the torrent. NAUT_ERR_NOTFOUND if unknown. */
naut_err naut_session_remove(naut_session *s, uint64_t id);
bool naut_session_has(const naut_session *s, uint64_t id);
size_t naut_session_count(const naut_session *s);
/* Resolve `id` and relocate one completed file to `dest` (the storage half of
* "move files as they finish"). NAUT_ERR_NOTFOUND if the torrent is unknown. */
naut_err naut_session_move_file(naut_session *s, uint64_t id,
uint32_t file_index, const char *dest);
#endif /* NAUT_SESSION_H */

46
include/naut/storage.h Normal file
View file

@ -0,0 +1,46 @@
/* storage.h — file backend mapping the torrent's flat byte space to files.
*
* A torrent is one contiguous byte space [0, total_length); this layer splits a
* read/write at any global offset across the underlying files (a single block
* write can straddle a file boundary). Phase 3 uses positional pread/pwrite for
* correctness; the io_uring O_DIRECT fast path is a Phase 6 swap behind this
* same interface. The backend is a vtable so a memory/object-store backend can
* be registered later (the "extensible" goal).
*/
#ifndef NAUT_STORAGE_H
#define NAUT_STORAGE_H
#include "naut/common.h"
#include "naut/metainfo.h"
typedef struct naut_storage naut_storage;
typedef struct {
bool direct_io;
bool preallocate;
} naut_storage_opts;
/* Open (creating + preallocating) all files under `root`. */
naut_storage *naut_storage_open(const naut_file *files, size_t nfiles,
const char *root, naut_err *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);
void naut_storage_close(naut_storage *s);
naut_err naut_storage_write(naut_storage *s, int64_t offset, const void *buf, size_t len);
naut_err naut_storage_read(naut_storage *s, int64_t offset, void *buf, size_t len);
naut_err naut_storage_sync(naut_storage *s);
/* Move one completed file out to `dest` (rename, or copy+unlink across file
* systems). The caller must guarantee the file is complete every piece
* overlapping it verified so no further writes target it. After this the slot
* is "externalized": subsequent I/O to its region returns NAUT_ERR_RANGE. This
* is the storage half of the "move files as they finish" feature. */
naut_err naut_storage_relocate(naut_storage *s, size_t file_index, const char *dest);
int64_t naut_storage_total(const naut_storage *s);
bool naut_storage_direct_enabled(const naut_storage *s);
#endif /* NAUT_STORAGE_H */

10
include/naut/system.h Normal file
View file

@ -0,0 +1,10 @@
/* system.h - CPU and memory placement controls for reactor threads. */
#ifndef NAUT_SYSTEM_H
#define NAUT_SYSTEM_H
#include "naut/common.h"
naut_err naut_pin_current_thread(int cpu);
int naut_online_cpus(void);
#endif /* NAUT_SYSTEM_H */

68
include/naut/tracker.h Normal file
View file

@ -0,0 +1,68 @@
/* tracker.h — HTTP and UDP tracker clients (BEP-3/BEP-23, BEP-15).
*
* Split into pure codec (URL building, bencode response parsing, UDP packet
* encode/decode all unit-testable without a socket) and thin blocking fetch
* helpers used by the swarm app. HTTPS/TLS is deferred to a later transport
* backend; the built-ins in this phase are plaintext HTTP and UDP.
*/
#ifndef NAUT_TRACKER_H
#define NAUT_TRACKER_H
#include "naut/common.h"
/* IPv4 compact peer (BEP-23). IPv6 (BEP-7) is a later addition. */
typedef struct { uint8_t ip[4]; uint16_t port; } naut_peer_addr;
typedef enum {
NAUT_TEV_NONE = 0, NAUT_TEV_COMPLETED = 1, NAUT_TEV_STARTED = 2, NAUT_TEV_STOPPED = 3
} naut_tracker_event; /* values match BEP-15 UDP event codes */
typedef struct {
uint8_t info_hash[20];
uint8_t peer_id[20];
uint16_t port;
uint64_t uploaded, downloaded, left;
naut_tracker_event event;
int32_t numwant; /* -1 for default */
uint32_t key;
} naut_announce_req;
typedef struct {
int32_t interval;
int32_t seeders, leechers; /* -1 if absent */
naut_peer_addr *peers;
size_t num_peers;
char *failure; /* tracker "failure reason", or NULL */
} naut_tracker_response;
void naut_tracker_response_free(naut_tracker_response *r);
/* --- HTTP --- */
/* Build the full announce GET URL (base?...params) with percent-encoded binary
* info_hash/peer_id. Returns bytes written (excl NUL) or 0 on overflow. */
size_t naut_tracker_http_url(const char *base, const naut_announce_req *req,
char *out, size_t outsz);
/* Parse a bencoded HTTP tracker response body (compact or dict peer list). */
naut_err naut_tracker_parse_http(const uint8_t *body, size_t len,
naut_tracker_response *out);
/* --- UDP (BEP-15): pure packet codec --- */
#define NAUT_UDP_CONNECT_REQ_LEN 16
#define NAUT_UDP_ANNOUNCE_REQ_LEN 98
void naut_udp_build_connect(uint8_t out[16], uint32_t txid);
naut_err naut_udp_parse_connect(const uint8_t *in, size_t len, uint32_t txid,
uint64_t *connection_id);
void naut_udp_build_announce(uint8_t out[98], uint64_t connection_id,
uint32_t txid, const naut_announce_req *req);
naut_err naut_udp_parse_announce(const uint8_t *in, size_t len, uint32_t txid,
naut_tracker_response *out);
/* --- live fetch helpers (blocking) --- */
/* HTTP GET the announce URL; fills out. Only http:// (no TLS yet). */
naut_err naut_tracker_announce_http(const char *url, naut_tracker_response *out);
/* Full UDP connect+announce handshake against host:port. */
naut_err naut_tracker_announce_udp(const char *host, uint16_t port,
const naut_announce_req *req,
naut_tracker_response *out);
#endif /* NAUT_TRACKER_H */

56
include/naut/uring.h Normal file
View file

@ -0,0 +1,56 @@
/* uring.h — io_uring lifecycle (the kernel-facing seam, part 2).
*
* Thin ownership wrapper over liburing so the rest of the engine never calls
* io_uring_* directly that keeps the "Linux-only now, portable later" seam
* intact (a future epoll/kqueue backend implements the same reactor contract).
* Each reactor thread owns exactly one naut_ring for both network and disk.
*/
#ifndef NAUT_URING_H
#define NAUT_URING_H
#include "naut/common.h"
#include "naut/buf.h"
#include <liburing.h>
typedef struct naut_ring {
struct io_uring ring;
bool sqpoll;
bool send_zc;
bool msg_ring;
bool buffers_registered;
bool recv_fixed;
} naut_ring;
/* entries: SQ depth (rounded up to a power of two by the kernel).
* sqpoll: dedicate a kernel thread to submission polling removes the
* io_uring_enter syscall from the hot path once the queue is warm (needs
* CAP_SYS_NICE or /proc/sys tuning on some setups; falls back if it can't). */
naut_err naut_ring_init(naut_ring *r, unsigned entries, bool sqpoll);
naut_err naut_ring_init_cpu(naut_ring *r, unsigned entries, bool sqpoll,
int sqpoll_cpu);
void naut_ring_close(naut_ring *r);
/* Detect kernel support for the features the data path relies on. Logs a
* summary; returns NAUT_ERR_NOSYS if a hard requirement is missing. */
naut_err naut_ring_probe(naut_ring *r);
/* Register the pool's contiguous slab as fixed buffer index 0. Registration
* may fail under a low RLIMIT_MEMLOCK; callers can continue in degraded mode. */
naut_err naut_ring_register_bufpool(naut_ring *r, const naut_bufpool *pool);
void naut_ring_unregister_buffers(naut_ring *r);
/* Prepare a send using SEND_ZC when supported and requested, otherwise normal
* SEND. Returns true when the caller must retain the buffer until a CQE with
* IORING_CQE_F_NOTIF arrives. */
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);
/* Returns true when this recv was armed against the registered fixed buffer.
* Some kernels accept READ_FIXED but reject IORING_RECVSEND_FIXED_BUF on plain
* recv with -EINVAL; the caller should remember this per-operation so it can
* distinguish "fixed-buffer unsupported, retry unfixed" from a real error
* (rather than tearing the connection down). */
bool naut_ring_prep_recv(naut_ring *r, struct io_uring_sqe *sqe, int fd,
void *buf, size_t len, int flags);
#endif /* NAUT_URING_H */

32
include/naut/worker.h Normal file
View file

@ -0,0 +1,32 @@
/* worker.h - bounded worker pool for hash/crypto jobs off the reactor path. */
#ifndef NAUT_WORKER_H
#define NAUT_WORKER_H
#include "naut/common.h"
typedef struct naut_worker_pool naut_worker_pool;
typedef struct naut_job naut_job;
typedef void (*naut_job_fn)(naut_job *job);
struct naut_job {
naut_job_fn run;
void *context;
naut_err result;
};
/* Jobs are caller-owned and must remain alive until popped from completions.
* queue_capacity must be a power of two. cpu_base < 0 disables affinity. */
naut_worker_pool *naut_worker_pool_create(uint32_t threads,
size_t queue_capacity,
int cpu_base);
void naut_worker_pool_destroy(naut_worker_pool *pool);
bool naut_worker_submit(naut_worker_pool *pool, naut_job *job);
bool naut_worker_complete(naut_worker_pool *pool, naut_job **job);
/* Readable when one or more jobs complete. The caller drains the eventfd and
* then pops completions. */
int naut_worker_eventfd(const naut_worker_pool *pool);
uint32_t naut_worker_threads(const naut_worker_pool *pool);
#endif /* NAUT_WORKER_H */