Skip to main content

dht_crawler/
types.rs

1use serde::{Deserialize, Serialize};
2use std::net::SocketAddr;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5/// IP families on which the DHT server listens and crawls.
6pub enum NetMode {
7    /// Bind and crawl IPv4 only.
8    Ipv4Only,
9    /// Bind and crawl IPv6 only.
10    Ipv6Only,
11    #[default]
12    /// Bind separate IPv4 and IPv6 sockets.
13    DualStack,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17/// Validated torrent metadata delivered to the application callback.
18pub struct TorrentInfo {
19    /// Lowercase hexadecimal SHA1 of the bencoded info dictionary.
20    pub info_hash: String,
21    /// Magnet URI containing the InfoHash.
22    pub magnet_link: String,
23    /// Torrent display name.
24    pub name: String,
25    /// Sum of file sizes in bytes.
26    pub total_size: u64,
27    /// Files described by the torrent.
28    pub files: Vec<FileInfo>,
29    /// Torrent piece length in bytes, or zero if absent.
30    pub piece_length: u64,
31    /// Peer addresses used to obtain the Metadata.
32    pub peers: Vec<String>,
33    /// Completion time as Unix seconds.
34    pub timestamp: u64,
35}
36
37/// Final outcome of a metadata fetch that passed the admission callback.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum MetadataFetchCompletionStatus {
40    /// Metadata was fetched and accepted by the torrent callback.
41    Accepted,
42    /// All available peer candidates failed.
43    FetchFailed,
44    /// Metadata was fetched, but the application did not accept it.
45    DeliveryRejected,
46}
47
48/// Report emitted exactly once after an admitted metadata fetch finishes.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct MetadataFetchCompletion {
51    /// InfoHash that reached a terminal state.
52    pub info_hash: String,
53    /// Final download/delivery status.
54    pub status: MetadataFetchCompletionStatus,
55    /// Real Peer network attempts; failure-cache skips are excluded.
56    pub attempts: usize,
57}
58
59impl MetadataFetchCompletion {
60    /// Returns true only for [`MetadataFetchCompletionStatus::Accepted`].
61    pub fn is_success(&self) -> bool {
62        self.status == MetadataFetchCompletionStatus::Accepted
63    }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67/// One file entry from the validated info dictionary.
68pub struct FileInfo {
69    /// Slash-separated relative path.
70    pub path: String,
71    /// File size in bytes.
72    pub size: u64,
73}
74
75#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
76/// Compact DHT node tuple used by crawl and routing code.
77pub struct NodeTuple {
78    /// Twenty-byte DHT node ID.
79    pub id: [u8; 20],
80    /// Public UDP endpoint.
81    pub addr: SocketAddr,
82}
83
84impl TorrentInfo {
85    /// Formats [`Self::total_size`] using binary thresholds and a short unit suffix.
86    pub fn format_size(&self) -> String {
87        format_bytes(self.total_size)
88    }
89}
90
91impl FileInfo {
92    /// Formats [`Self::size`] using binary thresholds and a short unit suffix.
93    pub fn format_size(&self) -> String {
94        format_bytes(self.size)
95    }
96}
97
98fn format_bytes(bytes: u64) -> String {
99    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
100    let mut size = bytes as f64;
101    let mut unit_index = 0;
102    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
103        size /= 1024.0;
104        unit_index += 1;
105    }
106    format!("{size:.2} {}", UNITS[unit_index])
107}
108
109#[derive(Debug, Clone)]
110/// Complete server configuration.
111pub struct DHTOptions {
112    /// UDP listen port.
113    pub port: u16,
114    /// Enabled IP families.
115    pub netmode: NetMode,
116    /// Capacity between announce processing and the Metadata scheduler.
117    pub hash_queue_capacity: usize,
118    /// Metadata download and Peer-cache limits.
119    pub metadata: MetadataOptions,
120    /// Active get_peers lookup rate and concurrency limits.
121    pub peer_lookup: PeerLookupOptions,
122    /// Active crawl, node-pool and scheduler limits.
123    pub crawl: CrawlOptions,
124}
125
126#[derive(Debug, Clone)]
127/// Metadata download and failure-cache limits.
128pub struct MetadataOptions {
129    /// End-to-end timeout for one Peer attempt, in seconds.
130    pub timeout_secs: u64,
131    /// Maximum number of deduplicated pending InfoHashes.
132    pub max_queue_size: usize,
133    /// Maximum number of concurrent Metadata jobs.
134    pub max_worker_count: usize,
135    /// Maximum number of cached bad Peer socket addresses.
136    pub peer_failure_cache_capacity: usize,
137    /// Timeout/connect failure cache lifetime in seconds.
138    pub peer_failure_ttl_secs: u64,
139}
140
141#[derive(Debug, Clone)]
142/// Active get_peers lookup budgets used to discover additional Metadata Peers.
143pub struct PeerLookupOptions {
144    /// Maximum new InfoHash lookups started per second. Zero disables active lookup.
145    pub max_lookups_per_second: u32,
146    /// Maximum lookup budget consumed immediately after an idle period.
147    pub burst: u32,
148    /// Maximum InfoHash lookups kept active at the same time.
149    pub max_active_lookups: usize,
150}
151
152#[derive(Debug, Clone, Default)]
153/// Active crawl configuration grouped by responsibility.
154pub struct CrawlOptions {
155    /// FIFO node-pool and responsive-ring limits.
156    pub pool: PoolOptions,
157    /// Query, replacement and response budgets.
158    pub rate_limit: RateLimitOptions,
159    /// Bootstrap sources and retry policy.
160    pub bootstrap: BootstrapOptions,
161    /// Target-generation policy.
162    pub target: TargetOptions,
163    /// Internal bounded-channel and snapshot limits.
164    pub scheduler: SchedulerOptions,
165}
166
167#[derive(Debug, Clone)]
168/// Independent crawl and UDP-response budgets.
169pub struct RateLimitOptions {
170    /// Maximum active find_node queries scheduled per second.
171    pub max_find_node_rate_per_sec: u32,
172    /// Maximum query budget consumed in one scheduler tick.
173    pub burst: u32,
174    /// Maximum total pending find_node transactions.
175    pub max_in_flight: usize,
176    /// Pending find_node timeout in seconds.
177    pub request_timeout_secs: u64,
178    /// Maximum never-before-probed destinations per minute.
179    pub max_new_destinations_per_minute: u32,
180    /// Maximum outbound DHT response packets per second.
181    pub max_response_rate_per_sec: u32,
182    /// Maximum encoded outbound DHT response bytes per second.
183    pub max_response_bytes_per_sec: u64,
184    /// Maximum response packets per source address per second.
185    pub max_response_rate_per_source: u32,
186    /// Remaining query-rate percentage when Metadata pressure reaches 95%.
187    pub metadata_pressure_floor_percent: u8,
188    /// Maximum FIFO replacements per minute after the pool has warmed.
189    pub max_replacements_per_minute: u32,
190    /// Maximum pending find_node transactions per IP subnet.
191    pub max_in_flight_per_subnet: usize,
192}
193
194#[derive(Debug, Clone)]
195/// FIFO crawl-pool and responsive-node reservoir limits.
196pub struct PoolOptions {
197    /// Maximum queued crawl nodes.
198    pub capacity: usize,
199    /// How long a probed endpoint is blocked from readmission, in seconds.
200    pub recent_probe_ttl_secs: u64,
201    /// Maximum nodes retained for replies and revisit traffic.
202    pub responsive_capacity: usize,
203    /// Responsive-node lifetime in seconds.
204    pub responsive_ttl_secs: u64,
205    /// Pool size below which bootstrap is considered.
206    pub low_watermark: usize,
207}
208
209#[derive(Debug, Clone)]
210/// Bootstrap hostnames and retry timing.
211pub struct BootstrapOptions {
212    /// Host:port sources resolved when bootstrap is needed.
213    pub nodes: Vec<String>,
214    /// Minimum interval between bootstrap rounds, in seconds.
215    pub interval_secs: u64,
216    /// Maximum resolved endpoints selected in one round.
217    pub max_nodes_per_round: usize,
218    /// Initial failed-source backoff in seconds.
219    pub source_backoff_base_secs: u64,
220    /// Maximum failed-source backoff in seconds.
221    pub source_backoff_max_secs: u64,
222}
223
224#[derive(Debug, Clone)]
225/// Distribution used to generate find_node targets and sender IDs.
226pub struct TargetOptions {
227    /// Percentage of targets that are fully random.
228    pub random_walk_percent: u8,
229    /// Percentage of targets chosen from sparse routing buckets.
230    pub sparse_bucket_percent: u8,
231    /// Whether outbound sender IDs borrow the target's prefix.
232    pub neighbor_sender_id: bool,
233}
234
235#[derive(Debug, Clone)]
236/// Capacities and batch limits for the crawl actor.
237pub struct SchedulerOptions {
238    /// Capacity for response/bootstrap priority events.
239    pub priority_event_channel_capacity: usize,
240    /// Capacity for newly discovered node events.
241    pub discovery_event_channel_capacity: usize,
242    /// Maximum events drained per actor iteration.
243    pub event_batch_limit: usize,
244    /// Maximum discovery nodes drained per actor iteration.
245    pub node_batch_limit: usize,
246    /// Maximum responsive nodes published in the lock-free snapshot.
247    pub routing_snapshot_size: usize,
248    /// Snapshot publication interval in milliseconds.
249    pub snapshot_refresh_millis: u64,
250}
251
252impl Default for DHTOptions {
253    fn default() -> Self {
254        Self {
255            port: 6881,
256            netmode: NetMode::Ipv4Only,
257            hash_queue_capacity: 10_000,
258            metadata: MetadataOptions::default(),
259            peer_lookup: PeerLookupOptions::default(),
260            crawl: CrawlOptions::default(),
261        }
262    }
263}
264
265impl Default for MetadataOptions {
266    fn default() -> Self {
267        Self {
268            timeout_secs: 4,
269            max_queue_size: 10_000,
270            max_worker_count: 256,
271            peer_failure_cache_capacity: 200_000,
272            peer_failure_ttl_secs: 60,
273        }
274    }
275}
276
277impl Default for PeerLookupOptions {
278    fn default() -> Self {
279        Self {
280            max_lookups_per_second: 32,
281            burst: 32,
282            max_active_lookups: 64,
283        }
284    }
285}
286
287impl Default for RateLimitOptions {
288    fn default() -> Self {
289        Self {
290            max_find_node_rate_per_sec: 200,
291            burst: 40,
292            max_in_flight: 512,
293            request_timeout_secs: 2,
294            max_new_destinations_per_minute: 10_000,
295            max_response_rate_per_sec: 500,
296            max_response_bytes_per_sec: 1024 * 1024,
297            max_response_rate_per_source: 40,
298            metadata_pressure_floor_percent: 25,
299            max_replacements_per_minute: 25_000,
300            max_in_flight_per_subnet: 8,
301        }
302    }
303}
304
305impl Default for PoolOptions {
306    fn default() -> Self {
307        Self {
308            capacity: 100_000,
309            recent_probe_ttl_secs: 600,
310            responsive_capacity: 16_384,
311            responsive_ttl_secs: 900,
312            low_watermark: 10_000,
313        }
314    }
315}
316
317impl Default for BootstrapOptions {
318    fn default() -> Self {
319        Self {
320            nodes: vec![
321                "router.bittorrent.com:6881".to_string(),
322                "dht.transmissionbt.com:6881".to_string(),
323                "router.utorrent.com:6881".to_string(),
324                "dht.aelitis.com:6881".to_string(),
325            ],
326            interval_secs: 300,
327            max_nodes_per_round: 3,
328            source_backoff_base_secs: 300,
329            source_backoff_max_secs: 3_600,
330        }
331    }
332}
333
334impl Default for TargetOptions {
335    fn default() -> Self {
336        Self {
337            random_walk_percent: 70,
338            sparse_bucket_percent: 30,
339            neighbor_sender_id: true,
340        }
341    }
342}
343
344impl Default for SchedulerOptions {
345    fn default() -> Self {
346        Self {
347            priority_event_channel_capacity: 8_192,
348            discovery_event_channel_capacity: 16_384,
349            event_batch_limit: 256,
350            node_batch_limit: 4_096,
351            routing_snapshot_size: 4_096,
352            snapshot_refresh_millis: 1_000,
353        }
354    }
355}