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
77
include/naut/bencode.h
Normal file
77
include/naut/bencode.h
Normal 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
49
include/naut/bitfield.h
Normal 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
63
include/naut/buf.h
Normal 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
59
include/naut/common.h
Normal 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
66
include/naut/dht.h
Normal 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
42
include/naut/event.h
Normal 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
70
include/naut/extension.h
Normal 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
45
include/naut/hash.h
Normal 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
47
include/naut/list.h
Normal 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
45
include/naut/log.h
Normal 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
40
include/naut/merkle.h
Normal 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
68
include/naut/metainfo.h
Normal 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
34
include/naut/mpmc.h
Normal 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
88
include/naut/mse.h
Normal 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 */
|
||||
49
include/naut/naut_plugin.h
Normal file
49
include/naut/naut_plugin.h
Normal 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
26
include/naut/net.h
Normal 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
72
include/naut/peer.h
Normal 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
86
include/naut/piece.h
Normal 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
29
include/naut/pipeline.h
Normal 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
25
include/naut/plugin.h
Normal 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
21
include/naut/rc4.h
Normal 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
45
include/naut/rpc.h
Normal 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
36
include/naut/script.h
Normal 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
41
include/naut/session.h
Normal 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
46
include/naut/storage.h
Normal 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
10
include/naut/system.h
Normal 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
68
include/naut/tracker.h
Normal 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
56
include/naut/uring.h
Normal 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
32
include/naut/worker.h
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue