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.
38#[must_use]
39pub fn announce_url_to_scrape(url: &str) -> Option<String> {
40    let last_pos = url.rfind("announce")?;
41    let mut result = String::with_capacity(url.len());
42    result.push_str(&url[..last_pos]);
43    result.push_str("scrape");
44    result.push_str(&url[last_pos + "announce".len()..]);
45    Some(result)
46}
47
48/// Common announce request parameters.
49#[derive(Debug, Clone)]
50pub struct AnnounceRequest {
51    /// SHA-1 info hash of the torrent.
52    pub info_hash: Id20,
53    /// Our 20-byte peer ID.
54    pub peer_id: Id20,
55    /// Port we are listening on.
56    pub port: u16,
57    /// Total bytes uploaded since last announce.
58    pub uploaded: u64,
59    /// Total bytes downloaded since last announce.
60    pub downloaded: u64,
61    /// Bytes remaining to download.
62    pub left: u64,
63    /// Optional announce event (started/stopped/completed).
64    pub event: AnnounceEvent,
65    /// Maximum number of peers to return (`None` = tracker default).
66    pub num_want: Option<i32>,
67    /// Request compact peer list (6 bytes per IPv4 peer).
68    pub compact: bool,
69    /// I2P destination Base64 string (BEP 7). Sent as `&i2p=<dest>` in HTTP announces.
70    pub i2p_destination: Option<String>,
71}
72
73/// Announce event type sent to trackers.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum AnnounceEvent {
76    /// Regular periodic re-announce (no event).
77    None = 0,
78    /// Download just finished -- all pieces verified.
79    Completed = 1,
80    /// First announce after adding the torrent.
81    Started = 2,
82    /// Torrent removed or client shutting down.
83    Stopped = 3,
84}
85
86/// Common announce response data.
87#[derive(Debug, Clone)]
88pub struct AnnounceResponse {
89    /// Re-announce interval in seconds.
90    pub interval: u32,
91    /// Number of seeders (optional).
92    pub seeders: Option<u32>,
93    /// Number of leechers (optional).
94    pub leechers: Option<u32>,
95    /// Peer addresses.
96    pub peers: Vec<SocketAddr>,
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn scrape_info_equality() {
105        let a = ScrapeInfo {
106            complete: 5,
107            incomplete: 3,
108            downloaded: 50,
109        };
110        let b = ScrapeInfo {
111            complete: 5,
112            incomplete: 3,
113            downloaded: 50,
114        };
115        assert_eq!(a, b);
116    }
117
118    #[test]
119    fn announce_to_scrape_url_http() {
120        assert_eq!(
121            announce_url_to_scrape("http://t.co/announce"),
122            Some("http://t.co/scrape".into()),
123        );
124    }
125
126    #[test]
127    fn announce_to_scrape_url_with_path() {
128        assert_eq!(
129            announce_url_to_scrape("http://t.co/path/announce"),
130            Some("http://t.co/path/scrape".into()),
131        );
132    }
133
134    #[test]
135    fn announce_to_scrape_url_no_announce() {
136        assert_eq!(announce_url_to_scrape("http://t.co/track"), None);
137    }
138}