Files
hse-2026/materials/07-naming-lookup/seminar/bittorrent/metadata.py
T
2026-10-21 23:03:28 +03:00

74 lines
2.7 KiB
Python

"""Inspect a torrent or demonstrate unchanged infohash and damaged piece detection."""
import argparse
import json
from pathlib import Path
import sys
from metainfo import decode, encode, inspect, make_metainfo, verify
def describe(raw):
meta, infohash = inspect(raw)
info = meta[b"info"]
print(json.dumps({
"announce": meta[b"announce"].decode(),
"name": info[b"name"].decode(),
"length": info[b"length"],
"piece length": info[b"piece length"],
"pieces (SHA-1)": [info[b"pieces"][i:i + 20].hex()
for i in range(0, len(info[b"pieces"]), 20)],
"infohash (raw bencoded info)": infohash,
}, indent=2))
def demo(directory):
directory.mkdir(parents=True, exist_ok=True)
data = bytes(range(256)) * 1024
original = encode(make_metainfo("lesson.bin", data))
meta, original_info = decode(original)
changed = dict(meta)
changed[b"announce"] = b"http://127.0.0.1:6970/announce"
changed[b"comment"] = b"A different comment"
modified = encode(changed)
assert decode(modified)[1] == original_info
damaged = bytearray(data)
damaged[65536 + 17] ^= 1
for name, content in [("lesson.bin", data), ("lesson.torrent", original),
("changed.torrent", modified), ("damaged.bin", damaged)]:
(directory / name).write_bytes(content)
describe(original)
print("Original infohash:", inspect(original)[1])
print("Changed infohash:", inspect(modified)[1])
assert inspect(original)[1] == inspect(modified)[1]
good, _ = verify(original, data)
bad, indices = verify(original, damaged)
assert good and not bad and indices == [1]
print("PASS: changing announce/comment preserved infohash")
print("PASS: original file verified; damaged file rejected, piece index:", indices)
print("Files:", directory)
def main():
parser = argparse.ArgumentParser(description=__doc__)
subs = parser.add_subparsers(dest="command", required=True)
p = subs.add_parser("demo")
p.add_argument("--directory", type=Path, default=Path(".local/metadata"))
p = subs.add_parser("inspect")
p.add_argument("torrent", type=Path)
p = subs.add_parser("verify")
p.add_argument("torrent", type=Path)
p.add_argument("file", type=Path)
args = parser.parse_args()
if args.command == "demo":
demo(args.directory)
elif args.command == "inspect":
describe(args.torrent.read_bytes())
else:
ok, bad = verify(args.torrent.read_bytes(), args.file.read_bytes())
print("OK" if ok else "FAILED", "bad pieces:", bad)
return 0 if ok else 1
return 0
if __name__ == "__main__":
sys.exit(main())