#!/usr/bin/env bash # Phase 4 tracker gate: discover a real libtorrent seed from a localhost HTTP # tracker, then complete the download without explicit peer arguments. set -u ROOT="$(cd "$(dirname "$0")/../.." && pwd)" SWARM="${1:-$ROOT/build/naut_swarm}" MODE="${2:-http}" SEEDER="$ROOT/tests/integration/seeder.py" if [ "$MODE" = "udp" ]; then TRACKER="$ROOT/tests/integration/udp_tracker.py" else TRACKER="$ROOT/tests/integration/http_tracker.py" fi SOURCE_TOR="$ROOT/tests/fixtures/single_v1.torrent" DATA="$ROOT/tests/fixtures/data" python3 -c 'import libtorrent' 2>/dev/null || { echo "SKIP: python libtorrent not available" exit 77 } [ -x "$SWARM" ] || { echo "FAIL: $SWARM not built"; exit 1; } out="$(mktemp -d /tmp/naut_tracker_swarm.XXXXXX)" seed_log="$(mktemp /tmp/naut_tracker_seed.XXXXXX)" tracker_log="$(mktemp /tmp/naut_http_tracker.XXXXXX)" swarm_log="$(mktemp /tmp/naut_tracker_client.XXXXXX)" tor="$(mktemp /tmp/naut_tracker.XXXXXX.torrent)" seed="" tracker="" cleanup() { [ -n "$seed" ] && kill "$seed" 2>/dev/null || true [ -n "$tracker" ] && kill "$tracker" 2>/dev/null || true rm -rf "$out" "$seed_log" "$tracker_log" "$swarm_log" "$tor" } trap cleanup EXIT python3 "$SEEDER" "$SOURCE_TOR" "$DATA" > "$seed_log" 2>&1 & seed=$! seed_port="" for _ in $(seq 1 100); do seed_port="$(grep -oP 'PORT \K[0-9]+' "$seed_log" 2>/dev/null || true)" [ -n "$seed_port" ] && [ "$seed_port" != 0 ] && break sleep 0.1 done if [ -z "$seed_port" ] || [ "$seed_port" = 0 ]; then echo "FAIL: seeder did not start" cat "$seed_log" exit 1 fi python3 "$TRACKER" "$seed_port" > "$tracker_log" 2>&1 & tracker=$! tracker_port="" for _ in $(seq 1 100); do tracker_port="$(grep -oP 'PORT \K[0-9]+' "$tracker_log" 2>/dev/null || true)" [ -n "$tracker_port" ] && break sleep 0.05 done if [ -z "$tracker_port" ]; then echo "FAIL: tracker did not start" cat "$tracker_log" exit 1 fi # Replace only the top-level dictionary. The raw info dictionary is copied # byte-for-byte so its SHA-1 info hash remains unchanged. if [ "$MODE" = "udp" ]; then announce="udp://127.0.0.1:$tracker_port/announce" else announce="http://127.0.0.1:$tracker_port/announce" fi backup="http://127.0.0.1:1/lower-tier-should-not-be-contacted" python3 - "$SOURCE_TOR" "$tor" "$announce" "$backup" <<'PY' import sys def skip(data, pos): token = data[pos] if token == ord("i"): return data.index(b"e", pos) + 1 if token in (ord("l"), ord("d")): pos += 1 while data[pos] != ord("e"): pos = skip(data, pos) if token == ord("d"): pos = skip(data, pos) return pos + 1 colon = data.index(b":", pos) size = int(data[pos:colon]) return colon + 1 + size source, target = sys.argv[1], sys.argv[2] announce, backup = sys.argv[3].encode(), sys.argv[4].encode() data = open(source, "rb").read() pos = 1 raw_info = None while data[pos] != ord("e"): colon = data.index(b":", pos) key_len = int(data[pos:colon]) key_start = colon + 1 key = data[key_start:key_start + key_len] pos = key_start + key_len value_start = pos pos = skip(data, pos) if key == b"info": raw_info = data[value_start:pos] assert raw_info is not None rewritten = ( b"d8:announce" + str(len(announce)).encode() + b":" + announce + b"13:announce-listll" + str(len(announce)).encode() + b":" + announce + b"el" + str(len(backup)).encode() + b":" + backup + b"ee" + b"4:info" + raw_info + b"e" ) open(target, "wb").write(rewritten) PY if ! timeout 30 "$SWARM" "$tor" "$out" > "$swarm_log" 2>&1; then echo "FAIL: tracker-discovered swarm did not complete" cat "$swarm_log" "$tracker_log" exit 1 fi if ! diff -r "$out/single.bin" "$DATA/single.bin" >/dev/null; then echo "FAIL: content mismatch" exit 1 fi if ! grep -q "REQUEST" "$tracker_log"; then echo "FAIL: tracker received no announce" exit 1 fi if grep -q "$backup" "$swarm_log"; then echo "FAIL: lower tracker tier was contacted after primary success" cat "$swarm_log" exit 1 fi echo "PASS: $MODE tracker discovery produced byte-identical output"