from __future__ import annotations import argparse import hashlib import os import libtorrent as lt from common import file_sha1, parse_size, write_manifest def deterministic_bytes(offset: int, size: int) -> bytes: out = bytearray() counter = offset // 32 while len(out) < size: out.extend(hashlib.sha256(f"torrent-peer-interop:{counter}".encode()).digest()) counter += 1 return bytes(out[:size]) def write_data(path: str, size: int) -> None: with open(path, "wb") as f: off = 0 while off < size: n = min(1024 * 1024, size - off) f.write(deterministic_bytes(off, n)) off += n def make_torrent(root: str, size: int, piece_size: int) -> str: os.makedirs(root, exist_ok=True) data_path = os.path.join(root, "data.bin") torrent_path = os.path.join(root, "test.torrent") write_data(data_path, size) fs = lt.file_storage() lt.add_files(fs, data_path) t = lt.create_torrent(fs, piece_size=piece_size) t.set_priv(True) t.add_tracker("http://fixture:9/announce") lt.set_piece_hashes(t, root) with open(torrent_path, "wb") as f: f.write(lt.bencode(t.generate())) return torrent_path def main() -> int: ap = argparse.ArgumentParser(description="Create the offline interop fixture.") ap.add_argument("--out", required=True) ap.add_argument("--size", default="32M") ap.add_argument("--piece-size", default="256K") args = ap.parse_args() size = parse_size(args.size) piece_size = parse_size(args.piece_size) torrent_path = make_torrent(args.out, size, piece_size) data_path = os.path.join(args.out, "data.bin") ti = lt.torrent_info(torrent_path) write_manifest( os.path.join(args.out, "manifest.json"), name=ti.name(), size=size, piece_size=piece_size, num_pieces=ti.num_pieces(), sha1=file_sha1(data_path), announce="http://fixture:9/announce", torrent=os.path.basename(torrent_path), data=os.path.basename(data_path), ) print(f"fixture ready: {size} bytes, {ti.num_pieces()} pieces", flush=True) return 0 if __name__ == "__main__": raise SystemExit(main())