Skip to main content

irontide_tracker/
lib.rs

1#![warn(missing_docs)]
2//! HTTP and UDP BitTorrent tracker clients (BEP 3, BEP 15, BEP 48).
3//!
4//! Supports HTTP and UDP tracker protocols for announce and scrape.
5
6/// Compact peer list encoding/decoding (BEP 23, BEP 7).
7pub mod compact;
8mod error;
9mod http;
10mod udp;
11
12pub use compact::{
13    encode_compact_peers, encode_compact_peers6, parse_compact_peers, parse_compact_peers6,
14};
15pub use error::{Error, Result};
16pub use http::{HttpAnnounceResponse, HttpScrapeResponse, HttpTracker};
17pub use udp::{UdpAnnounceResponse, UdpScrapeResponse, UdpTracker, UdpTrackerOption};
18
19use std::net::SocketAddr;
20
21use irontide_core::Id20;
22
23/// Scrape response data for a single info_hash (BEP 48).
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct ScrapeInfo {
26    /// Number of seeders (peers with complete file).
27    pub complete: u32,
28    /// Number of leechers (peers still downloading).
29    pub incomplete: u32,
30    /// Number of times the torrent has been fully downloaded.
31    pub downloaded: u32,
32}
33
34/// Convert an announce URL to a scrape URL (BEP 48).
35///
36/// Replaces the last occurrence of "announce" in the URL path with "scrape".
37/// Returns `None` if no "announce" is found in the URL.
38pub fn announce_url_to_scrape(url: &str) -> Option<String> {
39    let last_pos = url.rfind("announce")?;
40    let mut result = String::with_capacity(url.len());
41    result.push_str(&url[..last_pos]);
42    result.push_str("scrape");
43    result.push_str(&url[last_pos + "announce".len()..]);
44    Some(result)
45}
46
47/// Common announce request parameters.
48#[derive(Debug, Clone)]
49pub struct AnnounceRequest {
50    /// SHA-1 info hash of the torrent.
51    pub info_hash: Id20,
52    /// Our 20-byte peer ID.
53    pub peer_id: Id20,
54    /// Port we are listening on.
55    pub port: u16,
56    /// Total bytes uploaded since last announce.
57    pub uploaded: u64,
58    /// Total bytes downloaded since last announce.
59    pub downloaded: u64,
60    /// Bytes remaining to download.
61    pub left: u64,
62    /// Optional announce event (started/stopped/completed).
63    pub event: AnnounceEvent,
64    /// Maximum number of peers to return (`None` = tracker default).
65    pub num_want: Option<i32>,
66    /// Request compact peer list (6 bytes per IPv4 peer).
67    pub compact: bool,
68    /// I2P destination Base64 string (BEP 7). Sent as `&i2p=<dest>` in HTTP announces.
69    pub i2p_destination: Option<String>,
70}
71
72/// Announce event type sent to trackers.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum AnnounceEvent {
75    /// Regular periodic re-announce (no event).
76    None = 0,
77    /// Download just finished -- all pieces verified.
78    Completed = 1,
79    /// First announce after adding the torrent.
80    Started = 2,
81    /// Torrent removed or client shutting down.
82    Stopped = 3,
83}
84
85/// Common announce response data.
86#[derive(Debug, Clone)]
87pub struct AnnounceResponse {
88    /// Re-announce interval in seconds.
89    pub interval: u32,
90    /// Number of seeders (optional).
91    pub seeders: Option<u32>,
92    /// Number of leechers (optional).
93    pub leechers: Option<u32>,
94    /// Peer addresses.
95    pub peers: Vec<SocketAddr>,
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn scrape_info_equality() {
104        let a = ScrapeInfo {
105            complete: 5,
106            incomplete: 3,
107            downloaded: 50,
108        };
109        let b = ScrapeInfo {
110            complete: 5,
111            incomplete: 3,
112            downloaded: 50,
113        };
114        assert_eq!(a, b);
115    }
116
117    #[test]
118    fn announce_to_scrape_url_http() {
119        assert_eq!(
120            announce_url_to_scrape("http://t.co/announce"),
121            Some("http://t.co/scrape".into()),
122        );
123    }
124
125    #[test]
126    fn announce_to_scrape_url_with_path() {
127        assert_eq!(
128            announce_url_to_scrape("http://t.co/path/announce"),
129            Some("http://t.co/path/scrape".into()),
130        );
131    }
132
133    #[test]
134    fn announce_to_scrape_url_no_announce() {
135        assert_eq!(announce_url_to_scrape("http://t.co/track"), None);
136    }
137}