torrust_tracker/servers/http/v1/services/
scrape.rs

1//! The `scrape` service.
2//!
3//! The service is responsible for handling the `scrape` requests.
4//!
5//! It delegates the `scrape` logic to the [`Tracker`](crate::core::Tracker::scrape)
6//! and it returns the [`ScrapeData`] returned
7//! by the [`Tracker`].
8//!
9//! It also sends an [`statistics::Event`]
10//! because events are specific for the HTTP tracker.
11use std::net::IpAddr;
12use std::sync::Arc;
13
14use torrust_tracker_primitives::info_hash::InfoHash;
15
16use crate::core::{statistics, ScrapeData, Tracker};
17
18/// The HTTP tracker `scrape` service.
19///
20/// The service sends an statistics event that increments:
21///
22/// - The number of TCP connections handled by the HTTP tracker.
23/// - The number of TCP `scrape` requests handled by the HTTP tracker.
24///
25/// > **NOTICE**: as the HTTP tracker does not requires a connection request
26/// > like the UDP tracker, the number of TCP connections is incremented for
27/// > each `scrape` request.
28pub async fn invoke(tracker: &Arc<Tracker>, info_hashes: &Vec<InfoHash>, original_peer_ip: &IpAddr) -> ScrapeData {
29    let scrape_data = tracker.scrape(info_hashes).await;
30
31    send_scrape_event(original_peer_ip, tracker).await;
32
33    scrape_data
34}
35
36/// The HTTP tracker fake `scrape` service. It returns zeroed stats.
37///
38/// When the peer is not authenticated and the tracker is running in `private` mode,
39/// the tracker returns empty stats for all the torrents.
40///
41/// > **NOTICE**: tracker statistics are not updated in this case.
42pub async fn fake(tracker: &Arc<Tracker>, info_hashes: &Vec<InfoHash>, original_peer_ip: &IpAddr) -> ScrapeData {
43    send_scrape_event(original_peer_ip, tracker).await;
44
45    ScrapeData::zeroed(info_hashes)
46}
47
48async fn send_scrape_event(original_peer_ip: &IpAddr, tracker: &Arc<Tracker>) {
49    match original_peer_ip {
50        IpAddr::V4(_) => {
51            tracker.send_stats_event(statistics::Event::Tcp4Scrape).await;
52        }
53        IpAddr::V6(_) => {
54            tracker.send_stats_event(statistics::Event::Tcp6Scrape).await;
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61
62    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
63
64    use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes, PeerId};
65    use torrust_tracker_primitives::info_hash::InfoHash;
66    use torrust_tracker_primitives::{peer, DurationSinceUnixEpoch};
67    use torrust_tracker_test_helpers::configuration;
68
69    use crate::core::services::tracker_factory;
70    use crate::core::Tracker;
71
72    fn public_tracker() -> Tracker {
73        tracker_factory(&configuration::ephemeral_public())
74    }
75
76    fn sample_info_hashes() -> Vec<InfoHash> {
77        vec![sample_info_hash()]
78    }
79
80    fn sample_info_hash() -> InfoHash {
81        "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0".parse::<InfoHash>().unwrap()
82    }
83
84    fn sample_peer() -> peer::Peer {
85        peer::Peer {
86            peer_id: PeerId(*b"-qB00000000000000000"),
87            peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
88            updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
89            uploaded: NumberOfBytes::new(0),
90            downloaded: NumberOfBytes::new(0),
91            left: NumberOfBytes::new(0),
92            event: AnnounceEvent::Started,
93        }
94    }
95
96    mod with_real_data {
97
98        use std::future;
99        use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
100        use std::sync::Arc;
101
102        use mockall::predicate::eq;
103        use torrust_tracker_primitives::swarm_metadata::SwarmMetadata;
104        use torrust_tracker_test_helpers::configuration;
105
106        use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
107        use crate::servers::http::v1::services::scrape::invoke;
108        use crate::servers::http::v1::services::scrape::tests::{
109            public_tracker, sample_info_hash, sample_info_hashes, sample_peer,
110        };
111
112        #[tokio::test]
113        async fn it_should_return_the_scrape_data_for_a_torrent() {
114            let tracker = Arc::new(public_tracker());
115
116            let info_hash = sample_info_hash();
117            let info_hashes = vec![info_hash];
118
119            // Announce a new peer to force scrape data to contain not zeroed data
120            let mut peer = sample_peer();
121            let original_peer_ip = peer.ip();
122            tracker.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::All);
123
124            let scrape_data = invoke(&tracker, &info_hashes, &original_peer_ip).await;
125
126            let mut expected_scrape_data = ScrapeData::empty();
127            expected_scrape_data.add_file(
128                &info_hash,
129                SwarmMetadata {
130                    complete: 1,
131                    downloaded: 0,
132                    incomplete: 0,
133                },
134            );
135
136            assert_eq!(scrape_data, expected_scrape_data);
137        }
138
139        #[tokio::test]
140        async fn it_should_send_the_tcp_4_scrape_event_when_the_peer_uses_ipv4() {
141            let mut stats_event_sender_mock = statistics::MockEventSender::new();
142            stats_event_sender_mock
143                .expect_send_event()
144                .with(eq(statistics::Event::Tcp4Scrape))
145                .times(1)
146                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
147            let stats_event_sender = Box::new(stats_event_sender_mock);
148
149            let tracker = Arc::new(
150                Tracker::new(
151                    &configuration::ephemeral().core,
152                    Some(stats_event_sender),
153                    statistics::Repo::new(),
154                )
155                .unwrap(),
156            );
157
158            let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1));
159
160            invoke(&tracker, &sample_info_hashes(), &peer_ip).await;
161        }
162
163        #[tokio::test]
164        async fn it_should_send_the_tcp_6_scrape_event_when_the_peer_uses_ipv6() {
165            let mut stats_event_sender_mock = statistics::MockEventSender::new();
166            stats_event_sender_mock
167                .expect_send_event()
168                .with(eq(statistics::Event::Tcp6Scrape))
169                .times(1)
170                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
171            let stats_event_sender = Box::new(stats_event_sender_mock);
172
173            let tracker = Arc::new(
174                Tracker::new(
175                    &configuration::ephemeral().core,
176                    Some(stats_event_sender),
177                    statistics::Repo::new(),
178                )
179                .unwrap(),
180            );
181
182            let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969));
183
184            invoke(&tracker, &sample_info_hashes(), &peer_ip).await;
185        }
186    }
187
188    mod with_zeroed_data {
189
190        use std::future;
191        use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
192        use std::sync::Arc;
193
194        use mockall::predicate::eq;
195        use torrust_tracker_test_helpers::configuration;
196
197        use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
198        use crate::servers::http::v1::services::scrape::fake;
199        use crate::servers::http::v1::services::scrape::tests::{
200            public_tracker, sample_info_hash, sample_info_hashes, sample_peer,
201        };
202
203        #[tokio::test]
204        async fn it_should_always_return_the_zeroed_scrape_data_for_a_torrent() {
205            let tracker = Arc::new(public_tracker());
206
207            let info_hash = sample_info_hash();
208            let info_hashes = vec![info_hash];
209
210            // Announce a new peer to force scrape data to contain not zeroed data
211            let mut peer = sample_peer();
212            let original_peer_ip = peer.ip();
213            tracker.announce(&info_hash, &mut peer, &original_peer_ip, &PeersWanted::All);
214
215            let scrape_data = fake(&tracker, &info_hashes, &original_peer_ip).await;
216
217            let expected_scrape_data = ScrapeData::zeroed(&info_hashes);
218
219            assert_eq!(scrape_data, expected_scrape_data);
220        }
221
222        #[tokio::test]
223        async fn it_should_send_the_tcp_4_scrape_event_when_the_peer_uses_ipv4() {
224            let mut stats_event_sender_mock = statistics::MockEventSender::new();
225            stats_event_sender_mock
226                .expect_send_event()
227                .with(eq(statistics::Event::Tcp4Scrape))
228                .times(1)
229                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
230            let stats_event_sender = Box::new(stats_event_sender_mock);
231
232            let tracker = Arc::new(
233                Tracker::new(
234                    &configuration::ephemeral().core,
235                    Some(stats_event_sender),
236                    statistics::Repo::new(),
237                )
238                .unwrap(),
239            );
240
241            let peer_ip = IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1));
242
243            fake(&tracker, &sample_info_hashes(), &peer_ip).await;
244        }
245
246        #[tokio::test]
247        async fn it_should_send_the_tcp_6_scrape_event_when_the_peer_uses_ipv6() {
248            let mut stats_event_sender_mock = statistics::MockEventSender::new();
249            stats_event_sender_mock
250                .expect_send_event()
251                .with(eq(statistics::Event::Tcp6Scrape))
252                .times(1)
253                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
254            let stats_event_sender = Box::new(stats_event_sender_mock);
255
256            let tracker = Arc::new(
257                Tracker::new(
258                    &configuration::ephemeral().core,
259                    Some(stats_event_sender),
260                    statistics::Repo::new(),
261                )
262                .unwrap(),
263            );
264
265            let peer_ip = IpAddr::V6(Ipv6Addr::new(0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969, 0x6969));
266
267            fake(&tracker, &sample_info_hashes(), &peer_ip).await;
268        }
269    }
270}