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>
115 lines
5 KiB
C
115 lines
5 KiB
C
#include "naut/tracker.h"
|
|
#include "naut/bencode.h"
|
|
#include "test.h"
|
|
#include <string.h>
|
|
|
|
int main(void) {
|
|
naut_announce_req req;
|
|
memset(&req, 0, sizeof req);
|
|
for (int i = 0; i < 20; i++) { req.info_hash[i] = (uint8_t)i; req.peer_id[i] = (uint8_t)(0x80 + i); }
|
|
req.port = 6881; req.left = 1000; req.numwant = -1; req.key = 0xdeadbeef;
|
|
req.event = NAUT_TEV_STARTED;
|
|
|
|
/* --- HTTP announce URL --- */
|
|
char url[1024];
|
|
size_t n = naut_tracker_http_url("http://t.example/announce", &req, url, sizeof url);
|
|
CHECK(n > 0);
|
|
CHECK(strstr(url, "info_hash=%00%01%02") != NULL); /* binary pct-encoded */
|
|
CHECK(strstr(url, "port=6881") != NULL);
|
|
CHECK(strstr(url, "compact=1") != NULL);
|
|
CHECK(strstr(url, "event=started") != NULL);
|
|
/* base already having a query uses '&' */
|
|
naut_tracker_http_url("http://t.example/announce?x=1", &req, url, sizeof url);
|
|
CHECK(strstr(url, "announce?x=1&info_hash=") != NULL);
|
|
req.event = (naut_tracker_event)99;
|
|
CHECK(naut_tracker_http_url("http://t.example/announce", &req,
|
|
url, sizeof url) == 0);
|
|
req.event = NAUT_TEV_STARTED;
|
|
|
|
/* --- HTTP response parse: compact peers --- */
|
|
{
|
|
/* d8:intervali1800e5:peers12:<two 6-byte peers>e */
|
|
uint8_t body[128]; size_t b = 0;
|
|
const char *pre = "d8:intervali1800e8:completei5e10:incompletei2e5:peers12:";
|
|
memcpy(body, pre, strlen(pre)); b = strlen(pre);
|
|
uint8_t peers[12] = { 1,2,3,4, 0x1a,0xe1, 10,0,0,1, 0x1a,0xe2 };
|
|
memcpy(body + b, peers, 12); b += 12;
|
|
body[b++] = 'e';
|
|
|
|
naut_tracker_response r;
|
|
CHECK(naut_tracker_parse_http(body, b, &r) == NAUT_OK);
|
|
CHECK_EQ(r.interval, 1800);
|
|
CHECK_EQ(r.seeders, 5);
|
|
CHECK_EQ(r.leechers, 2);
|
|
CHECK_EQ(r.num_peers, 2);
|
|
CHECK(r.peers[0].ip[0]==1 && r.peers[0].ip[3]==4 && r.peers[0].port==0x1ae1);
|
|
CHECK(r.peers[1].ip[0]==10 && r.peers[1].port==0x1ae2);
|
|
naut_tracker_response_free(&r);
|
|
}
|
|
|
|
/* --- failure reason --- */
|
|
{
|
|
const char *body = "d14:failure reason17:torrent not founde";
|
|
naut_tracker_response r;
|
|
CHECK(naut_tracker_parse_http((const uint8_t *)body, strlen(body), &r) == NAUT_ERR_PROTO);
|
|
CHECK(r.failure && strcmp(r.failure, "torrent not found") == 0);
|
|
naut_tracker_response_free(&r);
|
|
}
|
|
|
|
/* --- UDP connect codec --- */
|
|
{
|
|
uint8_t pkt[98];
|
|
naut_udp_build_connect(pkt, 0x11223344);
|
|
/* protocol id 0x41727101980, action 0, txid */
|
|
CHECK(pkt[0]==0 && pkt[1]==0 && pkt[2]==0x04 && pkt[3]==0x17 &&
|
|
pkt[4]==0x27 && pkt[5]==0x10 && pkt[6]==0x19 && pkt[7]==0x80);
|
|
CHECK(pkt[8]==0 && pkt[11]==0); /* action connect */
|
|
CHECK(pkt[12]==0x11 && pkt[15]==0x44); /* txid */
|
|
|
|
/* build a fake connect response and parse it */
|
|
uint8_t resp[16] = {0};
|
|
resp[3] = 0; /* action connect */
|
|
resp[4]=0x11; resp[5]=0x22; resp[6]=0x33; resp[7]=0x44; /* txid */
|
|
for (int i = 0; i < 8; i++) resp[8+i] = (uint8_t)(0xA0 + i); /* conn id */
|
|
uint64_t cid = 0;
|
|
CHECK(naut_udp_parse_connect(resp, 16, 0x11223344, &cid) == NAUT_OK);
|
|
CHECK(cid == 0xA0A1A2A3A4A5A6A7ULL);
|
|
CHECK(naut_udp_parse_connect(resp, 16, 0x99999999, &cid) == NAUT_ERR_PROTO); /* wrong txid */
|
|
}
|
|
|
|
/* --- UDP announce codec round-trip --- */
|
|
{
|
|
uint8_t pkt[98];
|
|
naut_udp_build_announce(pkt, 0xA0A1A2A3A4A5A6A7ULL, 0x55667788, &req);
|
|
CHECK(pkt[11] == 1); /* action announce */
|
|
CHECK(memcmp(pkt + 16, req.info_hash, 20) == 0);
|
|
CHECK(memcmp(pkt + 36, req.peer_id, 20) == 0);
|
|
CHECK(pkt[83] == NAUT_TEV_STARTED); /* event low byte */
|
|
CHECK((pkt[96]<<8 | pkt[97]) == 6881); /* port */
|
|
|
|
/* fake announce response: action=1, txid, interval, leech, seed, 1 peer */
|
|
uint8_t resp[26] = {0};
|
|
resp[3] = 1;
|
|
resp[4]=0x55; resp[5]=0x66; resp[6]=0x77; resp[7]=0x88;
|
|
resp[11] = 0x84; /* interval 0x84 = 132 */
|
|
resp[15] = 3; /* leechers */
|
|
resp[19] = 7; /* seeders */
|
|
resp[20]=192; resp[21]=168; resp[22]=0; resp[23]=5; resp[24]=0x1a; resp[25]=0xe1;
|
|
naut_tracker_response r;
|
|
CHECK(naut_udp_parse_announce(resp, 26, 0x55667788, &r) == NAUT_OK);
|
|
CHECK_EQ(r.interval, 132);
|
|
CHECK_EQ(r.leechers, 3);
|
|
CHECK_EQ(r.seeders, 7);
|
|
CHECK_EQ(r.num_peers, 1);
|
|
CHECK(r.peers[0].ip[0]==192 && r.peers[0].ip[3]==5 && r.peers[0].port==0x1ae1);
|
|
naut_tracker_response_free(&r);
|
|
|
|
uint8_t malformed[27];
|
|
memcpy(malformed, resp, sizeof resp);
|
|
malformed[26] = 0;
|
|
CHECK(naut_udp_parse_announce(malformed, sizeof malformed,
|
|
0x55667788, &r) == NAUT_ERR_PROTO);
|
|
}
|
|
|
|
TEST_MAIN_END();
|
|
}
|