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>
100 lines
3.8 KiB
C
100 lines
3.8 KiB
C
#include "naut/bencode.h"
|
|
#include "test.h"
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
static naut_bc_doc *ok(const char *s) {
|
|
naut_bc_doc *d = NULL;
|
|
naut_err e = naut_bc_parse((const uint8_t *)s, strlen(s), &d);
|
|
if (e != NAUT_OK) { fprintf(stderr, " unexpected reject: \"%s\" (%s)\n", s, naut_strerror(e)); }
|
|
return e == NAUT_OK ? d : NULL;
|
|
}
|
|
static int rejects(const char *s, size_t len) {
|
|
naut_bc_doc *d = NULL;
|
|
naut_err e = naut_bc_parse((const uint8_t *)s, len, &d);
|
|
if (e == NAUT_OK) { naut_bc_free(d); return 0; }
|
|
return 1;
|
|
}
|
|
#define REJECT(s) CHECK(rejects(s, sizeof(s) - 1))
|
|
|
|
int main(void) {
|
|
/* integers */
|
|
naut_bc_doc *d;
|
|
int64_t iv;
|
|
d = ok("i42e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == 42); naut_bc_free(d);
|
|
d = ok("i0e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == 0); naut_bc_free(d);
|
|
d = ok("i-7e"); CHECK(d && naut_bc_get_int(naut_bc_root(d), &iv) && iv == -7); naut_bc_free(d);
|
|
REJECT("ie"); REJECT("i03e"); REJECT("i-0e"); REJECT("i-e"); REJECT("i1 e"); REJECT("i42");
|
|
|
|
/* strings (zero-copy + binary-safe) */
|
|
const uint8_t *sp; size_t sn;
|
|
d = ok("4:spam"); CHECK(d && naut_bc_get_str(naut_bc_root(d), &sp, &sn) && sn == 4 && !memcmp(sp,"spam",4));
|
|
/* slice points into the original buffer, not a copy */
|
|
naut_bc_free(d);
|
|
REJECT("4:spa"); REJECT("01:a"); /* leading zero length */
|
|
|
|
/* binary string with embedded NUL */
|
|
{
|
|
const char buf[] = "5:a\0b\0c"; /* len prefix "5:" then a \0 b \0 c */
|
|
naut_bc_doc *bd = NULL;
|
|
CHECK(naut_bc_parse((const uint8_t *)buf, 7, &bd) == NAUT_OK);
|
|
CHECK(bd && naut_bc_get_str(naut_bc_root(bd), &sp, &sn) && sn == 5 && sp[1] == 0 && sp[3] == 0);
|
|
naut_bc_free(bd);
|
|
}
|
|
|
|
/* list */
|
|
d = ok("l4:spami42ee");
|
|
CHECK(d);
|
|
const naut_bc *l = naut_bc_root(d);
|
|
CHECK(l && l->type == NAUT_BC_LIST && l->v.list.count == 2);
|
|
CHECK(naut_bc_str_eq(naut_bc_list_at(l, 0), "spam"));
|
|
CHECK(naut_bc_get_int(naut_bc_list_at(l, 1), &iv) && iv == 42);
|
|
naut_bc_free(d);
|
|
|
|
/* dict + raw span preservation (crucial for info-hash) */
|
|
d = ok("d3:bar4:spam3:fooi42ee");
|
|
const naut_bc *root = naut_bc_root(d);
|
|
CHECK(naut_bc_str_eq(naut_bc_dict_get(root, "bar"), "spam"));
|
|
CHECK(naut_bc_get_int(naut_bc_dict_get(root, "foo"), &iv) && iv == 42);
|
|
const naut_bc *bar = naut_bc_dict_get(root, "bar");
|
|
CHECK(bar->raw_len == 6 && memcmp(bar->raw, "4:spam", 6) == 0); /* exact encoding */
|
|
CHECK(naut_bc_dict_get(root, "missing") == NULL);
|
|
naut_bc_free(d);
|
|
|
|
/* structural rejects */
|
|
REJECT("i1ex"); /* trailing garbage */
|
|
REJECT("d3:bar4:spam"); /* unterminated dict */
|
|
REJECT("l1:a"); /* unterminated list */
|
|
REJECT("di1e1:ae"); /* non-string dict key */
|
|
REJECT(""); /* empty */
|
|
|
|
/* depth bomb: 200 nested lists must be rejected, not overflow the stack */
|
|
{
|
|
char bomb[512];
|
|
memset(bomb, 'l', sizeof bomb);
|
|
CHECK(rejects(bomb, sizeof bomb));
|
|
}
|
|
|
|
/* encoder round-trips back to an equal parse */
|
|
{
|
|
naut_bc_writer w; naut_bc_w_init(&w);
|
|
naut_bc_w_dict_begin(&w);
|
|
naut_bc_w_cstr(&w, "foo"); naut_bc_w_int(&w, 7);
|
|
naut_bc_w_cstr(&w, "list"); naut_bc_w_list_begin(&w);
|
|
naut_bc_w_cstr(&w, "x"); naut_bc_w_int(&w, -1);
|
|
naut_bc_w_end(&w);
|
|
naut_bc_w_end(&w);
|
|
CHECK(w.err == NAUT_OK);
|
|
|
|
naut_bc_doc *rd = NULL;
|
|
CHECK(naut_bc_parse(w.buf, w.len, &rd) == NAUT_OK);
|
|
const naut_bc *r = naut_bc_root(rd);
|
|
CHECK(naut_bc_get_int(naut_bc_dict_get(r, "foo"), &iv) && iv == 7);
|
|
const naut_bc *lst = naut_bc_dict_get(r, "list");
|
|
CHECK(lst && lst->type == NAUT_BC_LIST && lst->v.list.count == 2);
|
|
naut_bc_free(rd);
|
|
naut_bc_w_free(&w);
|
|
}
|
|
|
|
TEST_MAIN_END();
|
|
}
|