Skip to content

Repository files navigation

BitTorrent Client

A BitTorrent client written from scratch in Python — no third-party BitTorrent or bencode libraries. It parses .torrent files, announces to HTTP trackers, performs the peer wire protocol handshake, and downloads and verifies real file data from peers on the live BitTorrent network — including multi-file torrents.

This project started from CodeCrafters' "Build Your Own BitTorrent" challenge and was substantially refactored afterward: the original single-file, copy-pasted implementation was split into focused modules, a from-scratch bencode encoder was added (the original relied on a third-party library for encoding), the CLI was rebuilt on argparse instead of manual sys.argv parsing, and a full pytest test suite was added alongside peer retry/failover and multi-file torrent support.

What it does

  • Decodes and encodes bencode, BitTorrent's serialization format, entirely from scratch
  • Parses .torrent files and computes the info hash, for both single-file and multi-file torrents
  • Announces to an HTTP tracker and retrieves a peer list
  • Performs the BitTorrent peer handshake and message exchange (bitfield, interested, unchoke), with a real timeout on every socket read
  • Tries every peer in the list in order — if one fails to connect or refuses to cooperate, it automatically moves on to the next
  • Downloads individual pieces or entire torrents, verifying each piece's SHA1 hash against the .torrent file's metadata before accepting it
  • Reconstructs the correct folder/file hierarchy on disk for multi-file torrents
  • Backed by a pytest test suite covering the bencode codec, torrent parsing, peer protocol edge cases, and file-writing logic

Architecture

Module Responsibility
bencode.py Bencode decoding and encoding — no dependencies
torrent.py Torrent class: parses a .torrent file into a reusable object (announce URL, length, piece length, piece hashes, info hash, and file list for multi-file torrents)
tracker.py Announces to the tracker over HTTP and parses the compact peer list
peer.py The peer wire protocol — handshake, unchoke sequence with per-message timeouts, block-level piece requests with hash verification, and peer retry/failover
cli.py Command-line entry point (argparse), plus reconstruction of downloaded bytes into single or multiple files on disk

Each module only depends on the one(s) below it in this table, so each can be tested — and was tested — in isolation.

Setup

Requires Python 3.14+ and the requests library.

pip install requests

(or uv sync, if using the included uv.lock)

Usage

Run all commands from the repository root.

Decode a raw bencoded value:

python3 app/cli.py decode "d3:foo3:bare"

Show a torrent's metadata:

python3 app/cli.py info sample.torrent

List peers from the tracker:

python3 app/cli.py peers sample.torrent

Download a single piece:

python3 app/cli.py download_piece -o piece0.bin sample.torrent 0

Download a full torrent:

python3 app/cli.py download -o output.bin sample.torrent

For a multi-file torrent, -o is treated as a destination folder — the torrent's own name (from its metadata) becomes the actual folder created inside it, so downloading multiple torrents to the same -o never collides.

Testing

pip install pytest
pytest tests/
Test file Covers
test_bencode.py Decode/encode correctness for every bencode type, plus a full round-trip test
test_torrent.py Regression tests against a real sample .torrent, plus multi-file length/files calculations
test_peer.py waitForUnchoke's three real behaviors — success, choke, and unresponsive peer — using a fake socket, no real network needed
test_cli.py File-writing logic for both single-file and multi-file torrents, using real temporary directories

Known limitations

This is a functional client, not a production-grade one. Notable gaps, roughly in order of impact:

  • Single peer per download — no concurrent multi-peer downloading, so speed is limited to one connection; retry/failover currently only applies when establishing a connection, not if a peer drops mid-download
  • No request pipelining — blocks are requested one at a time, waiting for each response before sending the next
  • HTTP trackers only — no UDP tracker support, and no fallback across an announce-list
  • No magnet link support.torrent files only
  • Download only — the client doesn't seed/upload pieces to other peers
  • Two unrelated hardcoded peer IDs — the tracker announce and the peer handshake use different values rather than one shared identity; harmless today, but worth unifying

Possible next steps

  • Concurrent downloading across multiple peers, with pipelined block requests
  • Peer failover mid-download, not just at initial connection
  • UDP tracker support
  • Rarest-first piece selection
  • Magnet link support (BEP 9 metadata exchange)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages