Packages

BitTorrent engine for Elixir — download, seed, session persistence, and a stable OTP public API.

Current section

Files

Jump to
elixir_torrent CHANGELOG.md
Raw

CHANGELOG.md

# Changelog
## 0.6.2 - 2026-08-11
### Fixed
- Release pipeline: publishing to Hex.pm is now idempotent (skips cleanly if the tag's version is already on Hex.pm instead of failing), and cosign's release-signing step retries on transient OIDC-token fetch failures instead of falling through to an interactive device-code flow that stalls CI. No runtime/library changes.
## 0.6.1 - 2026-08-11
### Changed
- Runtime pin moved to Elixir 1.20.3 + OTP 29.0.5 (ERTS 17.0.5)
- Hackney 4.7.2, H2 0.11.0, QUIC 1.8.0, WebTransport 0.4.4 (HTTP stack behind HTTPoison, used by HTTP trackers, scrape, and BEP 19 web seeds)
## 0.6.0 - 2026-08-04
### Added
- **BEP 14 (LSD):** Local Service Discovery via UDP multicast (`BT-SEARCH`)
- **BEP 19 (WebSeed):** HTTP `Range` seeding from `url-list` via `Torrent.WebSeed`
- **BEP 48:** periodic tracker scrape; skip confirmed dead-swarm announce URLs
- **BEP 52 (phases 1–2/6):** detect v1/hybrid/v2, parse peers' v2 handshake capability; accept hybrid torrents/magnets; reject pure-v2 cleanly
- **Lazy piece storage:** on-demand piece read/write/verify workers with idle terminate
- **Peer.ConnectionManager:** outbound dial orchestration with per-family throttle
- Mid-download session checkpoint (bitfield + counters every 30s)
- Magnet `x.pe` peers kept as live dial candidates after metadata fetch
- Developer quality gate: warnings-as-errors compile, Dialyzer, and all enabled Credo checks at every priority via `mix quality`
### Changed
- `Magnet.Connection` sources its BEP 3 choke/unchoke/interested wire ids from `Peer.Const` instead of a local copy
- CI publish workflow renamed to `build-and-publish`, gated to run `mix hex.publish` on semver tag pushes
### Fixed
- NAT-PMP and UPnP passive UDP receive handling now decodes the datagram payload instead of the source-address tuple
- Global 2 MiB recv-buffer ceiling now bounds every wire frame's declared length, not only the six previously-capped ids
- Per-connection stall watchdog disconnects peers that trickle a declared frame length in forever instead of completing it
- Declared bitfield/piece frame lengths capped ahead of buffering, closing a memory-amplification gap alongside the existing LTEP/BEP 52 caps
- Tracker `started` is now resent on the first announce of every session (e.g. after a cold restart), not only once per info hash ever
## 0.5.1 - 2026-07-22
### Changed
- README: single-row shields.io badges at the top for release, Hex, HexDocs, changelog, source, Web UI, and macOS downloads
## 0.5.0 - 2026-07-22
### Added
- **BEP 9/10 (magnet + ut_metadata):** magnet URI parsing, `download_magnet/1`, LTEP extension protocol, metadata fetch/serve, magnet bootstrap and fetcher supervisors
- **BEP 11 (ut_pex):** peer exchange encode/decode, ingest, and broadcast over LTEP
- **BEP 32 (IPv6 DHT):** separate IPv4/IPv6 routing tables, compact node/peer codecs, dual-stack KRPC and lookup
- **BEP 42:** secure DHT node ID generation from the local IPv4 address
- **BEP 55 (ut_holepunch):** rendezvous/connect/forward hole-punch messages over LTEP; `Peer.Holepunch.Store` for punch state
- **DHT (BEP 5):** routing-table persistence across restarts, dual-stack socket wiring, runtime node-id path fix
- **NAT traversal:** NAT-PMP and UPnP IGD port-mapping clients, STUN mapping-behaviour detection, `NAT.PortMapper` orchestrator
- **MSE/PE:** Message Stream Encryption handshake and RC4 transport layer over `Peer.Transport`
- **uTP (BEP 29):** zombie-connection hardening and BEP 29 off-spec bug fixes
- **Peer discovery/dial:** `Peer.Endpoints`, sticky dial backoff with churn and hard-fail escalation, `Peer.DialStats` per address-family outcomes
- **Acceptor:** global IPv4/IPv6 address helpers, `IpCache` periodic `getifaddrs` snapshot via `:persistent_term`, dual-stack listen logging; LTEP handshakes advertise listen port and global addresses
- README: BEP 9, 10, 11, 32, 42, and 55 rows in Supported BEPs table; `download_magnet/1` in public API table
### Changed
- **Peer I/O:** MSE/PE encryption integrated into the peer transport and sender receive path
- **Peer dial:** immediate-drop churn backoff, endpoint PID lookup via `Peer.Endpoints.get_pid/3`
### Fixed
- DHT node ID persistence file path resolved at runtime instead of compile time
### Notes
- **Started at runtime** (via `ElixirTorrentApplication`): TCP acceptor (with `IpCache`), `PeerDiscovery`, magnet fetcher/bootstrap, and `Peer.Holepunch.Store`
- **Implemented and compiled, not started in supervision:** mainline DHT server, uTP dispatcher, `NAT.PortMapper`, `Peer.Endpoints`, `Peer.DialBackoff`, and `Peer.DialStats` — wiring deferred to a future release
- Default peer acquisition remains **TCP + tracker** (plus magnet metadata bootstrap); DHT announce, uTP fallback dials, NAT mapping, and full hole-punch initiation require the dormant services above
## 0.4.0 - 2026-07-08
### Added
- **BEP 29 (uTP):** `Peer.Transport` over TCP and uTP; uTP packet stack with LEDBAT; connection lifecycle and dispatcher; dial backoff and endpoint registry; outbound TCP-first dial with uTP fallback; inbound uTP handshake entry
- **BEP 5 (DHT):** Mainline DHT KRPC node (ping, find_node, get_peers, announce_peer); k-bucket routing table with bootstrap routers; iterative lookup and announce token workflow; DHT and uTP share one UDP socket via `DHT.send_udp/3`
- README: BEP 5 (Mainline DHT) row in Supported BEPs table
### Changed
- **Peer I/O:** `Peer.Sender` is the sole socket owner over `Peer.Transport`; `Peer.Receiver` removed; post-handshake socket handoff uses `Sender.activate/1`
### Fixed
- Torrent progress and `downloaded`/`left` counters reconciled with the on-disk bitfield
- Piece pipeline: idempotent piece statistics init, stale `:processing` cleanup, exclude-aware picker
- In-flight piece download recovery when subpiece requests stall (`waiting: []` no longer finishes early)
- Incoming piece block bounds validation; `Peer.log_id/1` for safe timeout logging
- Download scheduler: parallel piece picking with availability checks, resume handoff before controller scheduling, per-piece interested peer assignment
### Notes
- uTP (BEP 29) and DHT (BEP 5) are implemented and compiled in, but runtime activation (supervision wiring) is deferred to a future release; the default peer-acquisition/download path remains TCP + tracker-based.
## 0.3.0 - 2026-06-28
### Download location
- `ElixirTorrent.download/2` accepts an optional `:download_dir` keyword — base directory for downloaded files (defaults to `File.cwd!/0`)
- Session snapshots remain under `{File.cwd!()}/.elixir_torrent/state/` regardless of `:download_dir`
- `remove/2` with `delete_data: true` removes files from the torrent's download directory
### File layout
- Multi-file torrents whose files share no common top-level folder are written under a directory named after the torrent (sanitized `info.name`)
- Multi-file torrents that already use a shared root folder (e.g. `dir/a.bin`, `dir/b.bin`) keep their original paths
- Single-file torrents are written directly into the download root
### Docs
- README About section with project background
## 0.2.0 - 2026-06-11
### Session persistence
- Saved session state under `.elixir_torrent/state/{info_hash}.term` (relative to `File.cwd!/0`)
- On `download/1`, an existing session is loaded and the bitfield is verified against disk before resuming
- `remove/2` deletes the session file; `delete_data: true` also removes downloaded files
### Graceful shutdown API
- `stop_and_serialize/1` — stop piece downloads, disconnect peers (BEP 3), send tracker `event=stopped`, persist session, then stop the torrent process
- `stop_all_and_serialize/0` — same for every active torrent
- `list/0` — returns info hashes for all running torrent processes
### Peer disconnect
- Peers receive BEP 3 cancel / not interested / choke before TCP connections close
- Used during shutdown so peers are notified cleanly
## 0.1.2 - 2026-06-09
- `ElixirTorrent.list_files/1` — file list with per-file download progress
- `ElixirTorrent.remove/2` — stop a torrent; optional `delete_data: true` removes files from disk
- Requires Elixir `~> 1.20`
## 0.1.1 - 2026-02-22
- Published HexDocs for the public API (`ElixirTorrent`, `Torrents`)
- `ElixirTorrent.stats/2` documented as the preferred way to read runtime stats
## 0.1.0 - 2026-02-22
First public release — BitTorrent client **engine** publishable as a Hex dependency.
**Public API**
- `ElixirTorrent.download/1` — start a download from a `.torrent` file on disk
- `ElixirTorrent.stats/2` — poll name, speeds, and progress for a running torrent
- Escript entrypoint for CLI usage
**Protocol & networking**
- Peer wire protocol (BEP 3) with choking, rarest-first piece selection, and endgame mode
- Fast Extension (BEP 6) — `allowed_fast`, reject on choked requests
- IPv6 tracker peers (`peers6`) and dual-stack listen sockets
- Multi-homed HTTP announce (BEP 7) over IPv4 and IPv6
- HTTP and UDP trackers (BEP 15), compact peer lists (BEP 23)
**Reliability**
- Improved choke recovery, piece availability tracking, and tracker announce handling