rlibtorrent
A BitTorrent engine library in Rust: the protocol, peers, trackers, DHT, storage, hashing and resume, behind one crate. It is the engine inside rbittorrent.
Measured inside rbittorrent
Four clients race for one 1 GiB private torrent: rbittorrent, qBittorrent, Deluge and rTorrent, added in the same second and fed by the same 2 seeders. A local tracker refuses the torrent until a set tracker lag has passed, as a private tracker does with a brand-new upload. Each run records when each client was first answered by the tracker, when it finished, and how much it uploaded to the others. 25 runs over four setups, every finished copy checked byte for byte. How every number was taken.
| Setup | Registered first | Finished first | Uploaded most |
|---|---|---|---|
| SSD · T=0 s | 5 of 5 | 5 of 5 | 5 of 5 |
| SSD · T=5 s | 5 of 5 | 5 of 5 | 5 of 5 |
| HDD · T=0 s | 8 of 10 | 9 of 10 | 9 of 10 |
| HDD · T=5 s | 5 of 5 | 5 of 5 | 5 of 5 |
Runs rbittorrent won against the best of the other three clients in that run. A tie is not a win, and a client that never registered or finished loses that mark.
Where it leads
- SSD, tracker lag 0 s: finished first in 5 of 5 runs: 1.31 s against rTorrent's 3.11 s; uploaded the most in 5 of 5 runs: 69% of what the racing clients sent each other in the first 60 s, against rTorrent's 22%.
- SSD, tracker lag 5 s: registered first in 5 of 5 runs: 7.02 s after its fetch of the torrent, against rTorrent's 14.6 s; qBittorrent and Deluge did not register within the 60 s race; finished first in 5 of 5 runs: 8.12 s against rTorrent's 17.1 s; uploaded the most in 5 of 5 runs: 100% of what the racing clients sent each other in the first 60 s; the other clients sent nothing.
- HDD, tracker lag 0 s: finished first in 9 of 10 runs: 29.3 s against qBittorrent's 34.2 s; uploaded the most in 9 of 10 runs: 64% of what the racing clients sent each other in the first 60 s, against qBittorrent's 23%.
- HDD, tracker lag 5 s: registered first in 5 of 5 runs: 7.02 s after its fetch of the torrent, against rTorrent's 14.5 s; qBittorrent and Deluge did not register within the 60 s race; finished first in 5 of 5 runs: 14.3 s against rTorrent's 20.9 s; uploaded the most in 5 of 5 runs: 100% of what the racing clients sent each other in the first 60 s; the other clients sent nothing.
- 50 GiB, the spinning disk, one run each, 2026-10-13, one seeder on the same machine: finished in 16.9 min against qBittorrent's 22.2 min, at 83 MB of memory against 1,022 MB.
- 1,100 torrents in one client (2026-10-16): it held them in 40.1 MB against qBittorrent's 64.5 MB, and read the whole list in 19.9 ms against 86.3 ms (median of 50 reads).
Where it trails
- SSD, tracker lag 0 s, registration. Counted from each client's own fetch of the torrent, rTorrent reached the tracker in 2.3 ms (2.1 to 3.6 ms) and rbittorrent in 2.7 ms (1.8 to 8.1 ms). Counted from the race's start, which includes the order the clients were added in, rbittorrent was first in 5 of 5 runs.
- SSD, tracker lag 0 s, CPU time. rbittorrent 6.3 s (4.6 to 6.5 s), against qBittorrent 5.4 s (medians).
- HDD, tracker lag 0 s, registration. Counted from each client's own fetch of the torrent, rTorrent reached the tracker in 2.6 ms (1.9 to 3.1 ms) and rbittorrent in 3.1 ms (1.2 to 16.2 ms). Counted from the race's start, which includes the order the clients were added in, rbittorrent was first in 8 of 10 runs.
- HDD, tracker lag 0 s, CPU time. rbittorrent 5.6 s (2.8 to 7 s), against qBittorrent 5.4 s (medians).
- 2 GiB, the spinning disk, 5 runs each, 2026-10-15, the engine alone. qBittorrent finished in 8.69 s against 11.1 s, and checked the finished file again in 2.18 s against 12.9 s. A file this size still sits in memory, which qBittorrent's buffered writes use and ours, written straight to the disk, do not. Counted to the moment the data was on the disk, ours was sooner: 11.1 s against 13.3 s.
- Adding 1,100 torrents at once (2026-10-16): 1.47 s against qBittorrent's 1.28 s, each one through the client's own API.
- 50 GiB, the spinning disk, one run each, 2026-10-13, checking the finished file again: 7.4 min against qBittorrent's 7.2 min.
Time to finish the download (lower is better)
The numbers as a table
| Client | SSD · T=0 s | SSD · T=5 s | HDD · T=0 s | HDD · T=5 s |
|---|---|---|---|---|
| rbittorrent | 1.31 s (0.89 to 1.37 s) | 8.12 s (7.82 to 13 s) | 29.3 s (1.27 to 33.1 s) | 14.3 s (8.12 to 15.8 s) |
| qBittorrent | 6.73 s (6.5 to 8.31 s) | - | 34.2 s (27.9 to 41.1 s) | - |
| Deluge | 13.5 s, 1 of 5 runs | - | - | - |
| rTorrent | 3.11 s (2.24 to 3.53 s) | 17.1 s (16.5 to 17.7 s) | 40.4 s (26.6 to 47.3 s), 5 of 10 runs | 20.9 s (18 to 33.9 s) |
Median (fastest to slowest run), and how many runs produced a number when not all did. "-": none did.
Share of the racers' upload in the first 60 s (higher is better)
The numbers as a table
| Client | SSD · T=0 s | SSD · T=5 s | HDD · T=0 s | HDD · T=5 s |
|---|---|---|---|---|
| rbittorrent | 69% (59 to 73%) | 100% | 64% (5.3 to 72%) | 100% |
| qBittorrent | 6.1% (3.3 to 20%) | 0% | 23% (3.7 to 28%) | 0% |
| Deluge | 1.7% (0.3 to 4%) | 0% | 4.1% (0 to 7.2%) | 0% |
| rTorrent | 22% (15 to 29%) | 0% | 7.3% (4.8 to 91%) | 0% |
Median (fastest to slowest run), and how many runs produced a number when not all did. "-": none did.
Time to the tracker's first answer, from the client's own fetch of the torrent (lower is better)
The numbers as a table
| Client | SSD · T=0 s | SSD · T=5 s | HDD · T=0 s | HDD · T=5 s |
|---|---|---|---|---|
| rbittorrent | 2.7 ms (1.8 to 8.1 ms) | 7.02 s (7.01 to 7.03 s) | 3.1 ms (1.2 to 16.2 ms) | 7.02 s (7.02 to 7.03 s) |
| qBittorrent | 1.2 s (0.96 to 1.21 s) | - | 1 s (0.86 to 1.17 s) | - |
| Deluge | 495 ms (18.7 to 513 ms) | - | 481 ms (418 to 499 ms), 8 of 10 runs | - |
| rTorrent | 2.3 ms (2.1 to 3.6 ms) | 14.6 s (14.3 to 14.8 s) | 2.6 ms (1.9 to 3.1 ms) | 14.5 s (14.3 to 15 s) |
Median (fastest to slowest run), and how many runs produced a number when not all did. "-": none did.
What these numbers do not cover
- One machine: 12th Gen Intel(R) Core(TM) i7-12700K, 16 GB, Microsoft Windows 10 Pro. Another machine, disk or network can order the clients differently.
- One torrent size in the race (1 GiB), and seeders on the same machine rather than a public swarm over the internet.
- Transmission and uTorrent are not in the race.
- rTorrent's CPU time and memory are not measured: it runs inside WSL, where the sampler cannot see it.
Where one add spends its time
From the moment rbittorrent accepts an add to the moment the Web UI can show it: 5.5 ms, of which the engine's own steps are 1.93 ms. Median moments over 5 runs of the first SSD session.
- waits for the add queue: 1.1 ms
- checks the request and the disk room: 0.94 ms
- the engine adds the torrent: 1.89 ms
- the product writes its records: 0.59 ms
- hands the first announce to the engine: 0.04 ms
- publishes the new torrent to the UI: 0.94 ms
What is in it
26 crates, one front door. The README's own map:
crates/rlibtorrent | The front door. Re-exports the engine so a consumer depends on ONE name instead of nineteen; holds no behaviour of its own (ADR-0031). |
crates/rlibtorrent-types | Hashes, digests, Merkle arithmetic, and the parse budgets every other crate is held to. |
crates/rlibtorrent-bencode | The bencode parser and canonical encoder, with raw-span preservation so an info hash is taken over the bytes as received. |
crates/rlibtorrent-metainfo | .torrent files: v1, v2 (BEP 52), hybrid, path sanitisation, and torrent creation. |
crates/rlibtorrent-magnet | Magnet links, including hybrid btih plus btmh topics. |
crates/rlibtorrent-peer-wire | The peer protocol on the wire: handshake, framing, and the BEP 6/9/10/11 extensions. |
crates/rlibtorrent-peer-session | What a connection is allowed to do: the state machine, availability, and the request pipeline. |
crates/rlibtorrent-storage | Torrent offsets to file ranges, the storage backend contract, and the resume checkpoint. |
The other 21
crates/rlibtorrent-session | The policy engine: piece picking, choking, queueing, share limits, and categories. |
crates/rlibtorrent-tracker | HTTP and BEP 15 UDP announces, BEP 12 tiers, and the SSRF policy an announce is held to. |
crates/rlibtorrent-dht | BEP 5 with BEP 32 IPv6, routing table and lookups, socketless so a caller owns the UDP. |
crates/rlibtorrent-discovery | Where candidate peers come from, and the policy deciding which sources a torrent may use. |
crates/rlibtorrent-transport | TCP, SOCKS5, and the dial path every outbound connection shares. |
crates/rlibtorrent-utp | uTP and LEDBAT congestion control. |
crates/rlibtorrent-mse | Message Stream Encryption. |
crates/rlibtorrent-portmap | UPnP and NAT-PMP port mapping. |
crates/rlibtorrent-http | The HTTP client, its TLS trust decisions, and the SSRF policy shared with the tracker. |
crates/rlibtorrent-driver | The coordinator: the one place sockets, storage, discovery and the session meet. |
crates/rlibtorrent-api | The native control and event surface, its wire codec, and the delta stream. |
crates/rlibtorrent-webapi | The qBittorrent-compatible Web API contract, sans-io: no framework, no socket. |
crates/rlibtorrent-daemon | The executable, and the only crate that binds the Web API to a real HTTP transport. |
crates/rlibtorrent-capi | The C ABI, and the generated header a C or C++ caller links against. |
crates/rlibtorrent-qbt | Reading a real qBittorrent profile, read-only. |
lab/rlibtorrent-lab | The deterministic lab: virtual clock, seeded RNG, bounded runner, scripted peers. |
lab/oracle-libtorrent | Two pinned libtorrent builds (2.0.14 and 1.2.20) driven as subprocess oracles. |
lab/oracle-rqbit, lab/oracle-transmission | Two further independent implementations to differ against. |
crates/rlibtorrent-rss | RSS and Atom feeds, and the auto-download rules that turn articles into torrents. |
LunarWerxs/rlibusenet | Usenet, its own repository since ENG-12 (2026-09-18): NZB reading, article fetching, yEnc, PAR2, unpack and the job pipeline; this engine consumes it pinned by rev. |
xtask | The evidence machine: the ledger, the milestone state machine, and verify. |
Embedding it
The daemon crate runs the whole engine in your process and hands back its addresses; this is examples/embed-daemon with its comments left out:
use rlibtorrent_daemon::{ControlListen, Listen, Mechanisms, WebListen, start};
let started = start(
Listen::default(),
&checkpoints,
ControlListen::default(),
WebListen::default(),
None,
Mechanisms::default(),
)
.await?;
println!("engine embedded in-process:");
println!(" peer-wire listener: {}", started.local_addr());
println!(" control surface: {}", started.control_addr());
let report = started.stop().await?;3 examples, each run with cargo run from its folder:
examples/embed-daemon: Embeds the engine directly, the wayrlibtorrent-daemon's own binary doc says a caller may.examples/parse-and-build-torrent: Builds a.torrentfrom in-memory content, parses it back, and derives a magnet link, using nothing but the three leaf crates a Rust consumer would reach for first:rlibtorrent-metainfo,rlibtorrent-magnet,rlibtorrent-types.examples/seed-and-download: Two engines in one process: one seeds a torrent, the other downloads it, over a real TCP socket on this machine's loopback interface.
How it is checked
- 15 fuzz targets feed the parsers generated input, looking for a crash or a hang.
- Oracles: two pinned libtorrent builds (2.0.14 and 1.2.20), rqbit and Transmission, driven as subprocesses, so a difference in behaviour shows up as a failing comparison.
- A deterministic lab: a virtual clock, a seeded random source and scripted peers, so a failure replays exactly.
- The race above, run on the bench PC against the released clients.
Licence
PolyForm Noncommercial 1.0.0: free to use, change and embed for any noncommercial purpose. A product a business sells or runs on it needs a commercial licence. Source-available, not OSI open source.
Terms used here
- Tracker
- The server that tells each client which other clients have the torrent. A client “announces” to it to join.
- Announce
- A client's request to the tracker: here I am, who else has this torrent?
- Registered (Announce OK)
- The tracker's first answer that accepts the client. Until then the client gets no peers from it.
- Private torrent
- A torrent flagged so that clients find peers only through its tracker: no DHT, no peer exchange, no local discovery. Private trackers depend on it to count every byte.
- Tracker lag (T)
- How long the race's tracker answers “unregistered” after the start, as a private tracker does while a new upload is still being processed. T=0 s: it accepts at once.
- Seeder
- A client that has the whole torrent and only uploads.
- Swarm
- Every client sharing one torrent at that moment.
- Piece
- The torrent's unit of data (1 MiB here). Each one is checked against its hash before it counts, so a client never shares a broken piece.
- What one racing client sent the other racing clients, as a share of everything they sent each other. The seeders' upload is not counted.
- Median and range
- The middle run, and the fastest and slowest. A median is not moved by one odd run; the range shows how much the runs differ.
- Recheck
- Reading a finished file back and checking every piece's hash, as a client does after a crash or when it is moved.
- Peak private memory
- The most memory the client's process held for itself at any moment of the run.
- CPU time
- Processor time the client used during the run, counted in cycles and expressed in seconds of one core.
- Info hash
- The hash that names a torrent; two clients mean the same torrent when it matches.
- DHT
- The distributed table clients use to find peers without a tracker (BEP 5).
- Peer exchange (PEX)
- Clients telling each other about further peers.
- uTP
- BitTorrent over UDP, with LEDBAT congestion control so it yields to other traffic.
- Oracle
- An independent implementation the engine's behaviour is compared against, here libtorrent, rqbit and Transmission.
- Fuzz target
- A test that feeds a parser endless generated input, looking for a crash or a hang.