torrust_tracker/servers/udp/
handlers.rs

1//! Handlers for the UDP server.
2use std::fmt;
3use std::net::{IpAddr, SocketAddr};
4use std::panic::Location;
5use std::sync::Arc;
6use std::time::Instant;
7
8use aquatic_udp_protocol::{
9    AnnounceInterval, AnnounceRequest, AnnounceResponse, AnnounceResponseFixedData, ConnectRequest, ConnectResponse,
10    ErrorResponse, Ipv4AddrBytes, Ipv6AddrBytes, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer,
11    ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId,
12};
13use torrust_tracker_located_error::DynError;
14use torrust_tracker_primitives::info_hash::InfoHash;
15use tracing::{instrument, Level};
16use uuid::Uuid;
17use zerocopy::network_endian::I32;
18
19use super::connection_cookie::{check, from_connection_id, into_connection_id, make};
20use super::RawRequest;
21use crate::core::{statistics, PeersWanted, ScrapeData, Tracker};
22use crate::servers::udp::error::Error;
23use crate::servers::udp::logging::{log_bad_request, log_error_response, log_request, log_response};
24use crate::servers::udp::peer_builder;
25use crate::shared::bit_torrent::common::MAX_SCRAPE_TORRENTS;
26
27/// It handles the incoming UDP packets.
28///
29/// It's responsible for:
30///
31/// - Parsing the incoming packet.
32/// - Delegating the request to the correct handler depending on the request type.
33///
34/// It will return an `Error` response if the request is invalid.
35#[instrument(skip(udp_request, tracker, local_addr), ret(level = Level::TRACE))]
36pub(crate) async fn handle_packet(udp_request: RawRequest, tracker: &Tracker, local_addr: SocketAddr) -> Response {
37    tracing::debug!("Handling Packets: {udp_request:?}");
38
39    let start_time = Instant::now();
40
41    let request_id = RequestId::make(&udp_request);
42
43    match Request::parse_bytes(&udp_request.payload[..udp_request.payload.len()], MAX_SCRAPE_TORRENTS).map_err(|e| {
44        Error::InternalServer {
45            message: format!("{e:?}"),
46            location: Location::caller(),
47        }
48    }) {
49        Ok(request) => {
50            log_request(&request, &request_id, &local_addr);
51
52            let transaction_id = match &request {
53                Request::Connect(connect_request) => connect_request.transaction_id,
54                Request::Announce(announce_request) => announce_request.transaction_id,
55                Request::Scrape(scrape_request) => scrape_request.transaction_id,
56            };
57
58            let response = match handle_request(request, udp_request.from, tracker).await {
59                Ok(response) => response,
60                Err(e) => handle_error(&e, transaction_id),
61            };
62
63            let latency = start_time.elapsed();
64
65            log_response(&response, &transaction_id, &request_id, &local_addr, latency);
66
67            response
68        }
69        Err(e) => {
70            log_bad_request(&request_id);
71
72            let response = handle_error(
73                &Error::BadRequest {
74                    source: (Arc::new(e) as DynError).into(),
75                },
76                TransactionId(I32::new(0)),
77            );
78
79            log_error_response(&request_id);
80
81            response
82        }
83    }
84}
85
86/// It dispatches the request to the correct handler.
87///
88/// # Errors
89///
90/// If a error happens in the `handle_request` function, it will just return the  `ServerError`.
91#[instrument(skip(request, remote_addr, tracker))]
92pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: &Tracker) -> Result<Response, Error> {
93    tracing::trace!("handle request");
94
95    match request {
96        Request::Connect(connect_request) => handle_connect(remote_addr, &connect_request, tracker).await,
97        Request::Announce(announce_request) => handle_announce(remote_addr, &announce_request, tracker).await,
98        Request::Scrape(scrape_request) => handle_scrape(remote_addr, &scrape_request, tracker).await,
99    }
100}
101
102/// It handles the `Connect` request. Refer to [`Connect`](crate::servers::udp#connect)
103/// request for more information.
104///
105/// # Errors
106///
107/// This function does not ever return an error.
108#[instrument(skip(tracker), err, ret(level = Level::TRACE))]
109pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: &Tracker) -> Result<Response, Error> {
110    tracing::trace!("handle connect");
111
112    let connection_cookie = make(&remote_addr);
113    let connection_id = into_connection_id(&connection_cookie);
114
115    let response = ConnectResponse {
116        transaction_id: request.transaction_id,
117        connection_id,
118    };
119
120    // send stats event
121    match remote_addr {
122        SocketAddr::V4(_) => {
123            tracker.send_stats_event(statistics::Event::Udp4Connect).await;
124        }
125        SocketAddr::V6(_) => {
126            tracker.send_stats_event(statistics::Event::Udp6Connect).await;
127        }
128    }
129
130    Ok(Response::from(response))
131}
132
133/// It handles the `Announce` request. Refer to [`Announce`](crate::servers::udp#announce)
134/// request for more information.
135///
136/// # Errors
137///
138/// If a error happens in the `handle_announce` function, it will just return the  `ServerError`.
139#[instrument(skip(tracker), err, ret(level = Level::TRACE))]
140pub async fn handle_announce(
141    remote_addr: SocketAddr,
142    announce_request: &AnnounceRequest,
143    tracker: &Tracker,
144) -> Result<Response, Error> {
145    tracing::trace!("handle announce");
146
147    // Authentication
148    if tracker.requires_authentication() {
149        return Err(Error::TrackerAuthenticationRequired {
150            location: Location::caller(),
151        });
152    }
153
154    check(&remote_addr, &from_connection_id(&announce_request.connection_id))?;
155
156    let info_hash = announce_request.info_hash.into();
157    let remote_client_ip = remote_addr.ip();
158
159    // Authorization
160    tracker.authorize(&info_hash).await.map_err(|e| Error::TrackerError {
161        source: (Arc::new(e) as Arc<dyn std::error::Error + Send + Sync>).into(),
162    })?;
163
164    let mut peer = peer_builder::from_request(announce_request, &remote_client_ip);
165    let peers_wanted: PeersWanted = i32::from(announce_request.peers_wanted.0).into();
166
167    let response = tracker.announce(&info_hash, &mut peer, &remote_client_ip, &peers_wanted);
168
169    match remote_client_ip {
170        IpAddr::V4(_) => {
171            tracker.send_stats_event(statistics::Event::Udp4Announce).await;
172        }
173        IpAddr::V6(_) => {
174            tracker.send_stats_event(statistics::Event::Udp6Announce).await;
175        }
176    }
177
178    #[allow(clippy::cast_possible_truncation)]
179    if remote_addr.is_ipv4() {
180        let announce_response = AnnounceResponse {
181            fixed: AnnounceResponseFixedData {
182                transaction_id: announce_request.transaction_id,
183                announce_interval: AnnounceInterval(I32::new(i64::from(tracker.get_announce_policy().interval) as i32)),
184                leechers: NumberOfPeers(I32::new(i64::from(response.stats.incomplete) as i32)),
185                seeders: NumberOfPeers(I32::new(i64::from(response.stats.complete) as i32)),
186            },
187            peers: response
188                .peers
189                .iter()
190                .filter_map(|peer| {
191                    if let IpAddr::V4(ip) = peer.peer_addr.ip() {
192                        Some(ResponsePeer::<Ipv4AddrBytes> {
193                            ip_address: ip.into(),
194                            port: Port(peer.peer_addr.port().into()),
195                        })
196                    } else {
197                        None
198                    }
199                })
200                .collect(),
201        };
202
203        Ok(Response::from(announce_response))
204    } else {
205        let announce_response = AnnounceResponse {
206            fixed: AnnounceResponseFixedData {
207                transaction_id: announce_request.transaction_id,
208                announce_interval: AnnounceInterval(I32::new(i64::from(tracker.get_announce_policy().interval) as i32)),
209                leechers: NumberOfPeers(I32::new(i64::from(response.stats.incomplete) as i32)),
210                seeders: NumberOfPeers(I32::new(i64::from(response.stats.complete) as i32)),
211            },
212            peers: response
213                .peers
214                .iter()
215                .filter_map(|peer| {
216                    if let IpAddr::V6(ip) = peer.peer_addr.ip() {
217                        Some(ResponsePeer::<Ipv6AddrBytes> {
218                            ip_address: ip.into(),
219                            port: Port(peer.peer_addr.port().into()),
220                        })
221                    } else {
222                        None
223                    }
224                })
225                .collect(),
226        };
227
228        Ok(Response::from(announce_response))
229    }
230}
231
232/// It handles the `Scrape` request. Refer to [`Scrape`](crate::servers::udp#scrape)
233/// request for more information.
234///
235/// # Errors
236///
237/// This function does not ever return an error.
238#[instrument(skip(tracker), err, ret(level = Level::TRACE))]
239pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: &Tracker) -> Result<Response, Error> {
240    tracing::trace!("handle scrape");
241
242    // Convert from aquatic infohashes
243    let mut info_hashes: Vec<InfoHash> = vec![];
244    for info_hash in &request.info_hashes {
245        info_hashes.push((*info_hash).into());
246    }
247
248    let scrape_data = if tracker.requires_authentication() {
249        ScrapeData::zeroed(&info_hashes)
250    } else {
251        tracker.scrape(&info_hashes).await
252    };
253
254    let mut torrent_stats: Vec<TorrentScrapeStatistics> = Vec::new();
255
256    for file in &scrape_data.files {
257        let swarm_metadata = file.1;
258
259        #[allow(clippy::cast_possible_truncation)]
260        let scrape_entry = {
261            TorrentScrapeStatistics {
262                seeders: NumberOfPeers(I32::new(i64::from(swarm_metadata.complete) as i32)),
263                completed: NumberOfDownloads(I32::new(i64::from(swarm_metadata.downloaded) as i32)),
264                leechers: NumberOfPeers(I32::new(i64::from(swarm_metadata.incomplete) as i32)),
265            }
266        };
267
268        torrent_stats.push(scrape_entry);
269    }
270
271    // send stats event
272    match remote_addr {
273        SocketAddr::V4(_) => {
274            tracker.send_stats_event(statistics::Event::Udp4Scrape).await;
275        }
276        SocketAddr::V6(_) => {
277            tracker.send_stats_event(statistics::Event::Udp6Scrape).await;
278        }
279    }
280
281    let response = ScrapeResponse {
282        transaction_id: request.transaction_id,
283        torrent_stats,
284    };
285
286    Ok(Response::from(response))
287}
288
289fn handle_error(e: &Error, transaction_id: TransactionId) -> Response {
290    let message = e.to_string();
291    Response::from(ErrorResponse {
292        transaction_id,
293        message: message.into(),
294    })
295}
296
297/// An identifier for a request.
298#[derive(Debug, Clone)]
299pub struct RequestId(Uuid);
300
301impl RequestId {
302    fn make(_request: &RawRequest) -> RequestId {
303        RequestId(Uuid::new_v4())
304    }
305}
306
307impl fmt::Display for RequestId {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        write!(f, "{}", self.0)
310    }
311}
312
313#[cfg(test)]
314mod tests {
315
316    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
317    use std::sync::Arc;
318
319    use aquatic_udp_protocol::{NumberOfBytes, PeerId};
320    use torrust_tracker_clock::clock::Time;
321    use torrust_tracker_configuration::Configuration;
322    use torrust_tracker_primitives::peer;
323    use torrust_tracker_test_helpers::configuration;
324
325    use crate::core::services::tracker_factory;
326    use crate::core::Tracker;
327    use crate::CurrentClock;
328
329    fn tracker_configuration() -> Configuration {
330        default_testing_tracker_configuration()
331    }
332
333    fn default_testing_tracker_configuration() -> Configuration {
334        configuration::ephemeral()
335    }
336
337    fn public_tracker() -> Arc<Tracker> {
338        initialized_tracker(&configuration::ephemeral_public())
339    }
340
341    fn private_tracker() -> Arc<Tracker> {
342        initialized_tracker(&configuration::ephemeral_private())
343    }
344
345    fn whitelisted_tracker() -> Arc<Tracker> {
346        initialized_tracker(&configuration::ephemeral_listed())
347    }
348
349    fn initialized_tracker(configuration: &Configuration) -> Arc<Tracker> {
350        tracker_factory(configuration).into()
351    }
352
353    fn sample_ipv4_remote_addr() -> SocketAddr {
354        sample_ipv4_socket_address()
355    }
356
357    fn sample_ipv6_remote_addr() -> SocketAddr {
358        sample_ipv6_socket_address()
359    }
360
361    fn sample_ipv4_socket_address() -> SocketAddr {
362        SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)
363    }
364
365    fn sample_ipv6_socket_address() -> SocketAddr {
366        SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8080)
367    }
368
369    #[derive(Debug, Default)]
370    pub struct TorrentPeerBuilder {
371        peer: peer::Peer,
372    }
373
374    impl TorrentPeerBuilder {
375        #[must_use]
376        pub fn new() -> Self {
377            Self {
378                peer: peer::Peer {
379                    updated: CurrentClock::now(),
380                    ..Default::default()
381                },
382            }
383        }
384
385        #[must_use]
386        pub fn with_peer_address(mut self, peer_addr: SocketAddr) -> Self {
387            self.peer.peer_addr = peer_addr;
388            self
389        }
390
391        #[must_use]
392        pub fn with_peer_id(mut self, peer_id: PeerId) -> Self {
393            self.peer.peer_id = peer_id;
394            self
395        }
396
397        #[must_use]
398        pub fn with_number_of_bytes_left(mut self, left: i64) -> Self {
399            self.peer.left = NumberOfBytes::new(left);
400            self
401        }
402
403        #[must_use]
404        pub fn into(self) -> peer::Peer {
405            self.peer
406        }
407    }
408
409    struct TrackerConfigurationBuilder {
410        configuration: Configuration,
411    }
412
413    impl TrackerConfigurationBuilder {
414        pub fn default() -> TrackerConfigurationBuilder {
415            let default_configuration = default_testing_tracker_configuration();
416            TrackerConfigurationBuilder {
417                configuration: default_configuration,
418            }
419        }
420
421        pub fn with_external_ip(mut self, external_ip: &str) -> Self {
422            self.configuration.core.net.external_ip = Some(external_ip.to_owned().parse().expect("valid IP address"));
423            self
424        }
425
426        pub fn into(self) -> Configuration {
427            self.configuration
428        }
429    }
430
431    mod connect_request {
432
433        use std::future;
434        use std::sync::Arc;
435
436        use aquatic_udp_protocol::{ConnectRequest, ConnectResponse, Response, TransactionId};
437        use mockall::predicate::eq;
438
439        use super::{sample_ipv4_socket_address, sample_ipv6_remote_addr, tracker_configuration};
440        use crate::core::{self, statistics};
441        use crate::servers::udp::connection_cookie::{into_connection_id, make};
442        use crate::servers::udp::handlers::handle_connect;
443        use crate::servers::udp::handlers::tests::{public_tracker, sample_ipv4_remote_addr};
444
445        fn sample_connect_request() -> ConnectRequest {
446            ConnectRequest {
447                transaction_id: TransactionId(0i32.into()),
448            }
449        }
450
451        #[tokio::test]
452        async fn a_connect_response_should_contain_the_same_transaction_id_as_the_connect_request() {
453            let request = ConnectRequest {
454                transaction_id: TransactionId(0i32.into()),
455            };
456
457            let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker())
458                .await
459                .unwrap();
460
461            assert_eq!(
462                response,
463                Response::Connect(ConnectResponse {
464                    connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())),
465                    transaction_id: request.transaction_id
466                })
467            );
468        }
469
470        #[tokio::test]
471        async fn a_connect_response_should_contain_a_new_connection_id() {
472            let request = ConnectRequest {
473                transaction_id: TransactionId(0i32.into()),
474            };
475
476            let response = handle_connect(sample_ipv4_remote_addr(), &request, &public_tracker())
477                .await
478                .unwrap();
479
480            assert_eq!(
481                response,
482                Response::Connect(ConnectResponse {
483                    connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())),
484                    transaction_id: request.transaction_id
485                })
486            );
487        }
488
489        #[tokio::test]
490        async fn it_should_send_the_upd4_connect_event_when_a_client_tries_to_connect_using_a_ip4_socket_address() {
491            let mut stats_event_sender_mock = statistics::MockEventSender::new();
492            stats_event_sender_mock
493                .expect_send_event()
494                .with(eq(statistics::Event::Udp4Connect))
495                .times(1)
496                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
497            let stats_event_sender = Box::new(stats_event_sender_mock);
498
499            let client_socket_address = sample_ipv4_socket_address();
500
501            let torrent_tracker = Arc::new(
502                core::Tracker::new(
503                    &tracker_configuration().core,
504                    Some(stats_event_sender),
505                    statistics::Repo::new(),
506                )
507                .unwrap(),
508            );
509            handle_connect(client_socket_address, &sample_connect_request(), &torrent_tracker)
510                .await
511                .unwrap();
512        }
513
514        #[tokio::test]
515        async fn it_should_send_the_upd6_connect_event_when_a_client_tries_to_connect_using_a_ip6_socket_address() {
516            let mut stats_event_sender_mock = statistics::MockEventSender::new();
517            stats_event_sender_mock
518                .expect_send_event()
519                .with(eq(statistics::Event::Udp6Connect))
520                .times(1)
521                .returning(|_| Box::pin(future::ready(Some(Ok(())))));
522            let stats_event_sender = Box::new(stats_event_sender_mock);
523
524            let torrent_tracker = Arc::new(
525                core::Tracker::new(
526                    &tracker_configuration().core,
527                    Some(stats_event_sender),
528                    statistics::Repo::new(),
529                )
530                .unwrap(),
531            );
532            handle_connect(sample_ipv6_remote_addr(), &sample_connect_request(), &torrent_tracker)
533                .await
534                .unwrap();
535        }
536    }
537
538    mod announce_request {
539
540        use std::net::Ipv4Addr;
541        use std::num::NonZeroU16;
542
543        use aquatic_udp_protocol::{
544            AnnounceActionPlaceholder, AnnounceEvent, AnnounceRequest, ConnectionId, NumberOfBytes, NumberOfPeers,
545            PeerId as AquaticPeerId, PeerKey, Port, TransactionId,
546        };
547
548        use crate::servers::udp::connection_cookie::{into_connection_id, make};
549        use crate::servers::udp::handlers::tests::sample_ipv4_remote_addr;
550
551        struct AnnounceRequestBuilder {
552            request: AnnounceRequest,
553        }
554
555        impl AnnounceRequestBuilder {
556            pub fn default() -> AnnounceRequestBuilder {
557                let client_ip = Ipv4Addr::new(126, 0, 0, 1);
558                let client_port = 8080;
559                let info_hash_aquatic = aquatic_udp_protocol::InfoHash([0u8; 20]);
560
561                let default_request = AnnounceRequest {
562                    connection_id: into_connection_id(&make(&sample_ipv4_remote_addr())),
563                    action_placeholder: AnnounceActionPlaceholder::default(),
564                    transaction_id: TransactionId(0i32.into()),
565                    info_hash: info_hash_aquatic,
566                    peer_id: AquaticPeerId([255u8; 20]),
567                    bytes_downloaded: NumberOfBytes(0i64.into()),
568                    bytes_uploaded: NumberOfBytes(0i64.into()),
569                    bytes_left: NumberOfBytes(0i64.into()),
570                    event: AnnounceEvent::Started.into(),
571                    ip_address: client_ip.into(),
572                    key: PeerKey::new(0i32),
573                    peers_wanted: NumberOfPeers::new(1i32),
574                    port: Port::new(NonZeroU16::new(client_port).expect("a non-zero client port")),
575                };
576                AnnounceRequestBuilder {
577                    request: default_request,
578                }
579            }
580
581            pub fn with_connection_id(mut self, connection_id: ConnectionId) -> Self {
582                self.request.connection_id = connection_id;
583                self
584            }
585
586            pub fn with_info_hash(mut self, info_hash: aquatic_udp_protocol::InfoHash) -> Self {
587                self.request.info_hash = info_hash;
588                self
589            }
590
591            pub fn with_peer_id(mut self, peer_id: AquaticPeerId) -> Self {
592                self.request.peer_id = peer_id;
593                self
594            }
595
596            pub fn with_ip_address(mut self, ip_address: Ipv4Addr) -> Self {
597                self.request.ip_address = ip_address.into();
598                self
599            }
600
601            pub fn with_port(mut self, port: u16) -> Self {
602                self.request.port = Port(port.into());
603                self
604            }
605
606            pub fn into(self) -> AnnounceRequest {
607                self.request
608            }
609        }
610
611        mod using_ipv4 {
612
613            use std::future;
614            use std::net::{IpAddr, Ipv4Addr, SocketAddr};
615            use std::sync::Arc;
616
617            use aquatic_udp_protocol::{
618                AnnounceInterval, AnnounceResponse, InfoHash as AquaticInfoHash, Ipv4AddrBytes, Ipv6AddrBytes, NumberOfPeers,
619                PeerId as AquaticPeerId, Response, ResponsePeer,
620            };
621            use mockall::predicate::eq;
622
623            use crate::core::{self, statistics};
624            use crate::servers::udp::connection_cookie::{into_connection_id, make};
625            use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder;
626            use crate::servers::udp::handlers::tests::{
627                public_tracker, sample_ipv4_socket_address, tracker_configuration, TorrentPeerBuilder,
628            };
629            use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData};
630
631            #[tokio::test]
632            async fn an_announced_peer_should_be_added_to_the_tracker() {
633                let tracker = public_tracker();
634
635                let client_ip = Ipv4Addr::new(126, 0, 0, 1);
636                let client_port = 8080;
637                let info_hash = AquaticInfoHash([0u8; 20]);
638                let peer_id = AquaticPeerId([255u8; 20]);
639
640                let remote_addr = SocketAddr::new(IpAddr::V4(client_ip), client_port);
641
642                let request = AnnounceRequestBuilder::default()
643                    .with_connection_id(into_connection_id(&make(&remote_addr)))
644                    .with_info_hash(info_hash)
645                    .with_peer_id(peer_id)
646                    .with_ip_address(client_ip)
647                    .with_port(client_port)
648                    .into();
649
650                handle_announce(remote_addr, &request, &tracker).await.unwrap();
651
652                let peers = tracker.get_torrent_peers(&info_hash.0.into());
653
654                let expected_peer = TorrentPeerBuilder::new()
655                    .with_peer_id(peer_id)
656                    .with_peer_address(SocketAddr::new(IpAddr::V4(client_ip), client_port))
657                    .into();
658
659                assert_eq!(peers[0], Arc::new(expected_peer));
660            }
661
662            #[tokio::test]
663            async fn the_announced_peer_should_not_be_included_in_the_response() {
664                let remote_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080);
665
666                let request = AnnounceRequestBuilder::default()
667                    .with_connection_id(into_connection_id(&make(&remote_addr)))
668                    .into();
669
670                let response = handle_announce(remote_addr, &request, &public_tracker()).await.unwrap();
671
672                let empty_peer_vector: Vec<ResponsePeer<Ipv4AddrBytes>> = vec![];
673                assert_eq!(
674                    response,
675                    Response::from(AnnounceResponse {
676                        fixed: AnnounceResponseFixedData {
677                            transaction_id: request.transaction_id,
678                            announce_interval: AnnounceInterval(120i32.into()),
679                            leechers: NumberOfPeers(0i32.into()),
680                            seeders: NumberOfPeers(1i32.into()),
681                        },
682                        peers: empty_peer_vector
683                    })
684                );
685            }
686
687            #[tokio::test]
688            async fn the_tracker_should_always_use_the_remote_client_ip_but_not_the_port_in_the_udp_request_header_instead_of_the_peer_address_in_the_announce_request(
689            ) {
690                // From the BEP 15 (https://www.bittorrent.org/beps/bep_0015.html):
691                // "Do note that most trackers will only honor the IP address field under limited circumstances."
692
693                let tracker = public_tracker();
694
695                let info_hash = AquaticInfoHash([0u8; 20]);
696                let peer_id = AquaticPeerId([255u8; 20]);
697                let client_port = 8080;
698
699                let remote_client_ip = Ipv4Addr::new(126, 0, 0, 1);
700                let remote_client_port = 8081;
701                let peer_address = Ipv4Addr::new(126, 0, 0, 2);
702
703                let remote_addr = SocketAddr::new(IpAddr::V4(remote_client_ip), remote_client_port);
704
705                let request = AnnounceRequestBuilder::default()
706                    .with_connection_id(into_connection_id(&make(&remote_addr)))
707                    .with_info_hash(info_hash)
708                    .with_peer_id(peer_id)
709                    .with_ip_address(peer_address)
710                    .with_port(client_port)
711                    .into();
712
713                handle_announce(remote_addr, &request, &tracker).await.unwrap();
714
715                let peers = tracker.get_torrent_peers(&info_hash.0.into());
716
717                assert_eq!(peers[0].peer_addr, SocketAddr::new(IpAddr::V4(remote_client_ip), client_port));
718            }
719
720            fn add_a_torrent_peer_using_ipv6(tracker: &Arc<core::Tracker>) {
721                let info_hash = AquaticInfoHash([0u8; 20]);
722
723                let client_ip_v4 = Ipv4Addr::new(126, 0, 0, 1);
724                let client_ip_v6 = client_ip_v4.to_ipv6_compatible();
725                let client_port = 8080;
726                let peer_id = AquaticPeerId([255u8; 20]);
727
728                let peer_using_ipv6 = TorrentPeerBuilder::new()
729                    .with_peer_id(peer_id)
730                    .with_peer_address(SocketAddr::new(IpAddr::V6(client_ip_v6), client_port))
731                    .into();
732
733                tracker.upsert_peer_and_get_stats(&info_hash.0.into(), &peer_using_ipv6);
734            }
735
736            async fn announce_a_new_peer_using_ipv4(tracker: Arc<core::Tracker>) -> Response {
737                let remote_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080);
738                let request = AnnounceRequestBuilder::default()
739                    .with_connection_id(into_connection_id(&make(&remote_addr)))
740                    .into();
741
742                handle_announce(remote_addr, &request, &tracker).await.unwrap()
743            }
744
745            #[tokio::test]
746            async fn when_the_announce_request_comes_from_a_client_using_ipv4_the_response_should_not_include_peers_using_ipv6() {
747                let tracker = public_tracker();
748
749                add_a_torrent_peer_using_ipv6(&tracker);
750
751                let response = announce_a_new_peer_using_ipv4(tracker.clone()).await;
752
753                // The response should not contain the peer using IPV6
754                let peers: Option<Vec<ResponsePeer<Ipv6AddrBytes>>> = match response {
755                    Response::AnnounceIpv6(announce_response) => Some(announce_response.peers),
756                    _ => None,
757                };
758                let no_ipv6_peers = peers.is_none();
759                assert!(no_ipv6_peers);
760            }
761
762            #[tokio::test]
763            async fn should_send_the_upd4_announce_event() {
764                let mut stats_event_sender_mock = statistics::MockEventSender::new();
765                stats_event_sender_mock
766                    .expect_send_event()
767                    .with(eq(statistics::Event::Udp4Announce))
768                    .times(1)
769                    .returning(|_| Box::pin(future::ready(Some(Ok(())))));
770                let stats_event_sender = Box::new(stats_event_sender_mock);
771
772                let tracker = Arc::new(
773                    core::Tracker::new(
774                        &tracker_configuration().core,
775                        Some(stats_event_sender),
776                        statistics::Repo::new(),
777                    )
778                    .unwrap(),
779                );
780
781                handle_announce(
782                    sample_ipv4_socket_address(),
783                    &AnnounceRequestBuilder::default().into(),
784                    &tracker,
785                )
786                .await
787                .unwrap();
788            }
789
790            mod from_a_loopback_ip {
791                use std::net::{IpAddr, Ipv4Addr, SocketAddr};
792                use std::sync::Arc;
793
794                use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId};
795
796                use crate::servers::udp::connection_cookie::{into_connection_id, make};
797                use crate::servers::udp::handlers::handle_announce;
798                use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder;
799                use crate::servers::udp::handlers::tests::{public_tracker, TorrentPeerBuilder};
800
801                #[tokio::test]
802                async fn the_peer_ip_should_be_changed_to_the_external_ip_in_the_tracker_configuration_if_defined() {
803                    let tracker = public_tracker();
804
805                    let client_ip = Ipv4Addr::new(127, 0, 0, 1);
806                    let client_port = 8080;
807                    let info_hash = AquaticInfoHash([0u8; 20]);
808                    let peer_id = AquaticPeerId([255u8; 20]);
809
810                    let remote_addr = SocketAddr::new(IpAddr::V4(client_ip), client_port);
811
812                    let request = AnnounceRequestBuilder::default()
813                        .with_connection_id(into_connection_id(&make(&remote_addr)))
814                        .with_info_hash(info_hash)
815                        .with_peer_id(peer_id)
816                        .with_ip_address(client_ip)
817                        .with_port(client_port)
818                        .into();
819
820                    handle_announce(remote_addr, &request, &tracker).await.unwrap();
821
822                    let peers = tracker.get_torrent_peers(&info_hash.0.into());
823
824                    let external_ip_in_tracker_configuration = tracker.get_maybe_external_ip().unwrap();
825
826                    let expected_peer = TorrentPeerBuilder::new()
827                        .with_peer_id(peer_id)
828                        .with_peer_address(SocketAddr::new(external_ip_in_tracker_configuration, client_port))
829                        .into();
830
831                    assert_eq!(peers[0], Arc::new(expected_peer));
832                }
833            }
834        }
835
836        mod using_ipv6 {
837
838            use std::future;
839            use std::net::{IpAddr, Ipv4Addr, SocketAddr};
840            use std::sync::Arc;
841
842            use aquatic_udp_protocol::{
843                AnnounceInterval, AnnounceResponse, InfoHash as AquaticInfoHash, Ipv4AddrBytes, Ipv6AddrBytes, NumberOfPeers,
844                PeerId as AquaticPeerId, Response, ResponsePeer,
845            };
846            use mockall::predicate::eq;
847
848            use crate::core::{self, statistics};
849            use crate::servers::udp::connection_cookie::{into_connection_id, make};
850            use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder;
851            use crate::servers::udp::handlers::tests::{
852                public_tracker, sample_ipv6_remote_addr, tracker_configuration, TorrentPeerBuilder,
853            };
854            use crate::servers::udp::handlers::{handle_announce, AnnounceResponseFixedData};
855
856            #[tokio::test]
857            async fn an_announced_peer_should_be_added_to_the_tracker() {
858                let tracker = public_tracker();
859
860                let client_ip_v4 = Ipv4Addr::new(126, 0, 0, 1);
861                let client_ip_v6 = client_ip_v4.to_ipv6_compatible();
862                let client_port = 8080;
863                let info_hash = AquaticInfoHash([0u8; 20]);
864                let peer_id = AquaticPeerId([255u8; 20]);
865
866                let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port);
867
868                let request = AnnounceRequestBuilder::default()
869                    .with_connection_id(into_connection_id(&make(&remote_addr)))
870                    .with_info_hash(info_hash)
871                    .with_peer_id(peer_id)
872                    .with_ip_address(client_ip_v4)
873                    .with_port(client_port)
874                    .into();
875
876                handle_announce(remote_addr, &request, &tracker).await.unwrap();
877
878                let peers = tracker.get_torrent_peers(&info_hash.0.into());
879
880                let expected_peer = TorrentPeerBuilder::new()
881                    .with_peer_id(peer_id)
882                    .with_peer_address(SocketAddr::new(IpAddr::V6(client_ip_v6), client_port))
883                    .into();
884
885                assert_eq!(peers[0], Arc::new(expected_peer));
886            }
887
888            #[tokio::test]
889            async fn the_announced_peer_should_not_be_included_in_the_response() {
890                let client_ip_v4 = Ipv4Addr::new(126, 0, 0, 1);
891                let client_ip_v6 = client_ip_v4.to_ipv6_compatible();
892
893                let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), 8080);
894
895                let request = AnnounceRequestBuilder::default()
896                    .with_connection_id(into_connection_id(&make(&remote_addr)))
897                    .into();
898
899                let response = handle_announce(remote_addr, &request, &public_tracker()).await.unwrap();
900
901                let empty_peer_vector: Vec<ResponsePeer<Ipv6AddrBytes>> = vec![];
902                assert_eq!(
903                    response,
904                    Response::from(AnnounceResponse {
905                        fixed: AnnounceResponseFixedData {
906                            transaction_id: request.transaction_id,
907                            announce_interval: AnnounceInterval(120i32.into()),
908                            leechers: NumberOfPeers(0i32.into()),
909                            seeders: NumberOfPeers(1i32.into()),
910                        },
911                        peers: empty_peer_vector
912                    })
913                );
914            }
915
916            #[tokio::test]
917            async fn the_tracker_should_always_use_the_remote_client_ip_but_not_the_port_in_the_udp_request_header_instead_of_the_peer_address_in_the_announce_request(
918            ) {
919                // From the BEP 15 (https://www.bittorrent.org/beps/bep_0015.html):
920                // "Do note that most trackers will only honor the IP address field under limited circumstances."
921
922                let tracker = public_tracker();
923
924                let info_hash = AquaticInfoHash([0u8; 20]);
925                let peer_id = AquaticPeerId([255u8; 20]);
926                let client_port = 8080;
927
928                let remote_client_ip = "::100".parse().unwrap(); // IPV4 ::0.0.1.0 -> IPV6 = ::100 = ::ffff:0:100 = 0:0:0:0:0:ffff:0:0100
929                let remote_client_port = 8081;
930                let peer_address = "126.0.0.1".parse().unwrap();
931
932                let remote_addr = SocketAddr::new(IpAddr::V6(remote_client_ip), remote_client_port);
933
934                let request = AnnounceRequestBuilder::default()
935                    .with_connection_id(into_connection_id(&make(&remote_addr)))
936                    .with_info_hash(info_hash)
937                    .with_peer_id(peer_id)
938                    .with_ip_address(peer_address)
939                    .with_port(client_port)
940                    .into();
941
942                handle_announce(remote_addr, &request, &tracker).await.unwrap();
943
944                let peers = tracker.get_torrent_peers(&info_hash.0.into());
945
946                // When using IPv6 the tracker converts the remote client ip into a IPv4 address
947                assert_eq!(peers[0].peer_addr, SocketAddr::new(IpAddr::V6(remote_client_ip), client_port));
948            }
949
950            fn add_a_torrent_peer_using_ipv4(tracker: &Arc<core::Tracker>) {
951                let info_hash = AquaticInfoHash([0u8; 20]);
952
953                let client_ip_v4 = Ipv4Addr::new(126, 0, 0, 1);
954                let client_port = 8080;
955                let peer_id = AquaticPeerId([255u8; 20]);
956
957                let peer_using_ipv4 = TorrentPeerBuilder::new()
958                    .with_peer_id(peer_id)
959                    .with_peer_address(SocketAddr::new(IpAddr::V4(client_ip_v4), client_port))
960                    .into();
961
962                tracker.upsert_peer_and_get_stats(&info_hash.0.into(), &peer_using_ipv4);
963            }
964
965            async fn announce_a_new_peer_using_ipv6(tracker: Arc<core::Tracker>) -> Response {
966                let client_ip_v4 = Ipv4Addr::new(126, 0, 0, 1);
967                let client_ip_v6 = client_ip_v4.to_ipv6_compatible();
968                let client_port = 8080;
969                let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port);
970                let request = AnnounceRequestBuilder::default()
971                    .with_connection_id(into_connection_id(&make(&remote_addr)))
972                    .into();
973
974                handle_announce(remote_addr, &request, &tracker).await.unwrap()
975            }
976
977            #[tokio::test]
978            async fn when_the_announce_request_comes_from_a_client_using_ipv6_the_response_should_not_include_peers_using_ipv4() {
979                let tracker = public_tracker();
980
981                add_a_torrent_peer_using_ipv4(&tracker);
982
983                let response = announce_a_new_peer_using_ipv6(tracker.clone()).await;
984
985                // The response should not contain the peer using IPV4
986                let peers: Option<Vec<ResponsePeer<Ipv4AddrBytes>>> = match response {
987                    Response::AnnounceIpv4(announce_response) => Some(announce_response.peers),
988                    _ => None,
989                };
990                let no_ipv4_peers = peers.is_none();
991                assert!(no_ipv4_peers);
992            }
993
994            #[tokio::test]
995            async fn should_send_the_upd6_announce_event() {
996                let mut stats_event_sender_mock = statistics::MockEventSender::new();
997                stats_event_sender_mock
998                    .expect_send_event()
999                    .with(eq(statistics::Event::Udp6Announce))
1000                    .times(1)
1001                    .returning(|_| Box::pin(future::ready(Some(Ok(())))));
1002                let stats_event_sender = Box::new(stats_event_sender_mock);
1003
1004                let tracker = Arc::new(
1005                    core::Tracker::new(
1006                        &tracker_configuration().core,
1007                        Some(stats_event_sender),
1008                        statistics::Repo::new(),
1009                    )
1010                    .unwrap(),
1011                );
1012
1013                let remote_addr = sample_ipv6_remote_addr();
1014
1015                let announce_request = AnnounceRequestBuilder::default()
1016                    .with_connection_id(into_connection_id(&make(&remote_addr)))
1017                    .into();
1018
1019                handle_announce(remote_addr, &announce_request, &tracker).await.unwrap();
1020            }
1021
1022            mod from_a_loopback_ip {
1023                use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
1024                use std::sync::Arc;
1025
1026                use aquatic_udp_protocol::{InfoHash as AquaticInfoHash, PeerId as AquaticPeerId};
1027
1028                use crate::core;
1029                use crate::core::statistics::Keeper;
1030                use crate::servers::udp::connection_cookie::{into_connection_id, make};
1031                use crate::servers::udp::handlers::handle_announce;
1032                use crate::servers::udp::handlers::tests::announce_request::AnnounceRequestBuilder;
1033                use crate::servers::udp::handlers::tests::TrackerConfigurationBuilder;
1034
1035                #[tokio::test]
1036                async fn the_peer_ip_should_be_changed_to_the_external_ip_in_the_tracker_configuration() {
1037                    let configuration = Arc::new(TrackerConfigurationBuilder::default().with_external_ip("::126.0.0.1").into());
1038                    let (stats_event_sender, stats_repository) = Keeper::new_active_instance();
1039                    let tracker =
1040                        Arc::new(core::Tracker::new(&configuration.core, Some(stats_event_sender), stats_repository).unwrap());
1041
1042                    let loopback_ipv4 = Ipv4Addr::new(127, 0, 0, 1);
1043                    let loopback_ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
1044
1045                    let client_ip_v4 = loopback_ipv4;
1046                    let client_ip_v6 = loopback_ipv6;
1047                    let client_port = 8080;
1048
1049                    let info_hash = AquaticInfoHash([0u8; 20]);
1050                    let peer_id = AquaticPeerId([255u8; 20]);
1051
1052                    let remote_addr = SocketAddr::new(IpAddr::V6(client_ip_v6), client_port);
1053
1054                    let request = AnnounceRequestBuilder::default()
1055                        .with_connection_id(into_connection_id(&make(&remote_addr)))
1056                        .with_info_hash(info_hash)
1057                        .with_peer_id(peer_id)
1058                        .with_ip_address(client_ip_v4)
1059                        .with_port(client_port)
1060                        .into();
1061
1062                    handle_announce(remote_addr, &request, &tracker).await.unwrap();
1063
1064                    let peers = tracker.get_torrent_peers(&info_hash.0.into());
1065
1066                    let external_ip_in_tracker_configuration = tracker.get_maybe_external_ip().unwrap();
1067
1068                    assert!(external_ip_in_tracker_configuration.is_ipv6());
1069
1070                    // There's a special type of IPv6 addresses that provide compatibility with IPv4.
1071                    // The last 32 bits of these addresses represent an IPv4, and are represented like this:
1072                    // 1111:2222:3333:4444:5555:6666:1.2.3.4
1073                    //
1074                    // ::127.0.0.1 is the IPV6 representation for the IPV4 address 127.0.0.1.
1075                    assert_eq!(Ok(peers[0].peer_addr.ip()), "::126.0.0.1".parse());
1076                }
1077            }
1078        }
1079    }
1080
1081    mod scrape_request {
1082        use std::net::SocketAddr;
1083        use std::sync::Arc;
1084
1085        use aquatic_udp_protocol::{
1086            InfoHash, NumberOfDownloads, NumberOfPeers, PeerId, Response, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics,
1087            TransactionId,
1088        };
1089
1090        use super::TorrentPeerBuilder;
1091        use crate::core::{self};
1092        use crate::servers::udp::connection_cookie::{into_connection_id, make};
1093        use crate::servers::udp::handlers::handle_scrape;
1094        use crate::servers::udp::handlers::tests::{public_tracker, sample_ipv4_remote_addr};
1095
1096        fn zeroed_torrent_statistics() -> TorrentScrapeStatistics {
1097            TorrentScrapeStatistics {
1098                seeders: NumberOfPeers(0.into()),
1099                completed: NumberOfDownloads(0.into()),
1100                leechers: NumberOfPeers(0.into()),
1101            }
1102        }
1103
1104        #[tokio::test]
1105        async fn should_return_no_stats_when_the_tracker_does_not_have_any_torrent() {
1106            let remote_addr = sample_ipv4_remote_addr();
1107
1108            let info_hash = InfoHash([0u8; 20]);
1109            let info_hashes = vec![info_hash];
1110
1111            let request = ScrapeRequest {
1112                connection_id: into_connection_id(&make(&remote_addr)),
1113                transaction_id: TransactionId(0i32.into()),
1114                info_hashes,
1115            };
1116
1117            let response = handle_scrape(remote_addr, &request, &public_tracker()).await.unwrap();
1118
1119            let expected_torrent_stats = vec![zeroed_torrent_statistics()];
1120
1121            assert_eq!(
1122                response,
1123                Response::from(ScrapeResponse {
1124                    transaction_id: request.transaction_id,
1125                    torrent_stats: expected_torrent_stats
1126                })
1127            );
1128        }
1129
1130        async fn add_a_seeder(tracker: Arc<core::Tracker>, remote_addr: &SocketAddr, info_hash: &InfoHash) {
1131            let peer_id = PeerId([255u8; 20]);
1132
1133            let peer = TorrentPeerBuilder::new()
1134                .with_peer_id(peer_id)
1135                .with_peer_address(*remote_addr)
1136                .with_number_of_bytes_left(0)
1137                .into();
1138
1139            tracker.upsert_peer_and_get_stats(&info_hash.0.into(), &peer);
1140        }
1141
1142        fn build_scrape_request(remote_addr: &SocketAddr, info_hash: &InfoHash) -> ScrapeRequest {
1143            let info_hashes = vec![*info_hash];
1144
1145            ScrapeRequest {
1146                connection_id: into_connection_id(&make(remote_addr)),
1147                transaction_id: TransactionId::new(0i32),
1148                info_hashes,
1149            }
1150        }
1151
1152        async fn add_a_sample_seeder_and_scrape(tracker: Arc<core::Tracker>) -> Response {
1153            let remote_addr = sample_ipv4_remote_addr();
1154            let info_hash = InfoHash([0u8; 20]);
1155
1156            add_a_seeder(tracker.clone(), &remote_addr, &info_hash).await;
1157
1158            let request = build_scrape_request(&remote_addr, &info_hash);
1159
1160            handle_scrape(remote_addr, &request, &tracker).await.unwrap()
1161        }
1162
1163        fn match_scrape_response(response: Response) -> Option<ScrapeResponse> {
1164            match response {
1165                Response::Scrape(scrape_response) => Some(scrape_response),
1166                _ => None,
1167            }
1168        }
1169
1170        mod with_a_public_tracker {
1171            use aquatic_udp_protocol::{NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics};
1172
1173            use crate::servers::udp::handlers::tests::public_tracker;
1174            use crate::servers::udp::handlers::tests::scrape_request::{add_a_sample_seeder_and_scrape, match_scrape_response};
1175
1176            #[tokio::test]
1177            async fn should_return_torrent_statistics_when_the_tracker_has_the_requested_torrent() {
1178                let tracker = public_tracker();
1179
1180                let torrent_stats = match_scrape_response(add_a_sample_seeder_and_scrape(tracker.clone()).await);
1181
1182                let expected_torrent_stats = vec![TorrentScrapeStatistics {
1183                    seeders: NumberOfPeers(1.into()),
1184                    completed: NumberOfDownloads(0.into()),
1185                    leechers: NumberOfPeers(0.into()),
1186                }];
1187
1188                assert_eq!(torrent_stats.unwrap().torrent_stats, expected_torrent_stats);
1189            }
1190        }
1191
1192        mod with_a_private_tracker {
1193
1194            use aquatic_udp_protocol::InfoHash;
1195
1196            use crate::servers::udp::handlers::handle_scrape;
1197            use crate::servers::udp::handlers::tests::scrape_request::{
1198                add_a_sample_seeder_and_scrape, build_scrape_request, match_scrape_response, zeroed_torrent_statistics,
1199            };
1200            use crate::servers::udp::handlers::tests::{private_tracker, sample_ipv4_remote_addr};
1201
1202            #[tokio::test]
1203            async fn should_return_zeroed_statistics_when_the_tracker_does_not_have_the_requested_torrent() {
1204                let tracker = private_tracker();
1205
1206                let remote_addr = sample_ipv4_remote_addr();
1207                let non_existing_info_hash = InfoHash([0u8; 20]);
1208
1209                let request = build_scrape_request(&remote_addr, &non_existing_info_hash);
1210
1211                let torrent_stats = match_scrape_response(handle_scrape(remote_addr, &request, &tracker).await.unwrap()).unwrap();
1212
1213                let expected_torrent_stats = vec![zeroed_torrent_statistics()];
1214
1215                assert_eq!(torrent_stats.torrent_stats, expected_torrent_stats);
1216            }
1217
1218            #[tokio::test]
1219            async fn should_return_zeroed_statistics_when_the_tracker_has_the_requested_torrent_because_authenticated_requests_are_not_supported_in_udp_tracker(
1220            ) {
1221                let tracker = private_tracker();
1222
1223                let torrent_stats = match_scrape_response(add_a_sample_seeder_and_scrape(tracker.clone()).await).unwrap();
1224
1225                let expected_torrent_stats = vec![zeroed_torrent_statistics()];
1226
1227                assert_eq!(torrent_stats.torrent_stats, expected_torrent_stats);
1228            }
1229        }
1230
1231        mod with_a_whitelisted_tracker {
1232            use aquatic_udp_protocol::{InfoHash, NumberOfDownloads, NumberOfPeers, TorrentScrapeStatistics};
1233
1234            use crate::servers::udp::handlers::handle_scrape;
1235            use crate::servers::udp::handlers::tests::scrape_request::{
1236                add_a_seeder, build_scrape_request, match_scrape_response, zeroed_torrent_statistics,
1237            };
1238            use crate::servers::udp::handlers::tests::{sample_ipv4_remote_addr, whitelisted_tracker};
1239
1240            #[tokio::test]
1241            async fn should_return_the_torrent_statistics_when_the_requested_torrent_is_whitelisted() {
1242                let tracker = whitelisted_tracker();
1243
1244                let remote_addr = sample_ipv4_remote_addr();
1245                let info_hash = InfoHash([0u8; 20]);
1246
1247                add_a_seeder(tracker.clone(), &remote_addr, &info_hash).await;
1248
1249                tracker.add_torrent_to_memory_whitelist(&info_hash.0.into()).await;
1250
1251                let request = build_scrape_request(&remote_addr, &info_hash);
1252
1253                let torrent_stats = match_scrape_response(handle_scrape(remote_addr, &request, &tracker).await.unwrap()).unwrap();
1254
1255                let expected_torrent_stats = vec![TorrentScrapeStatistics {
1256                    seeders: NumberOfPeers(1.into()),
1257                    completed: NumberOfDownloads(0.into()),
1258                    leechers: NumberOfPeers(0.into()),
1259                }];
1260
1261                assert_eq!(torrent_stats.torrent_stats, expected_torrent_stats);
1262            }
1263
1264            #[tokio::test]
1265            async fn should_return_zeroed_statistics_when_the_requested_torrent_is_not_whitelisted() {
1266                let tracker = whitelisted_tracker();
1267
1268                let remote_addr = sample_ipv4_remote_addr();
1269                let info_hash = InfoHash([0u8; 20]);
1270
1271                add_a_seeder(tracker.clone(), &remote_addr, &info_hash).await;
1272
1273                let request = build_scrape_request(&remote_addr, &info_hash);
1274
1275                let torrent_stats = match_scrape_response(handle_scrape(remote_addr, &request, &tracker).await.unwrap()).unwrap();
1276
1277                let expected_torrent_stats = vec![zeroed_torrent_statistics()];
1278
1279                assert_eq!(torrent_stats.torrent_stats, expected_torrent_stats);
1280            }
1281        }
1282
1283        fn sample_scrape_request(remote_addr: &SocketAddr) -> ScrapeRequest {
1284            let info_hash = InfoHash([0u8; 20]);
1285            let info_hashes = vec![info_hash];
1286
1287            ScrapeRequest {
1288                connection_id: into_connection_id(&make(remote_addr)),
1289                transaction_id: TransactionId(0i32.into()),
1290                info_hashes,
1291            }
1292        }
1293
1294        mod using_ipv4 {
1295            use std::future;
1296            use std::sync::Arc;
1297
1298            use mockall::predicate::eq;
1299
1300            use super::sample_scrape_request;
1301            use crate::core::{self, statistics};
1302            use crate::servers::udp::handlers::handle_scrape;
1303            use crate::servers::udp::handlers::tests::{sample_ipv4_remote_addr, tracker_configuration};
1304
1305            #[tokio::test]
1306            async fn should_send_the_upd4_scrape_event() {
1307                let mut stats_event_sender_mock = statistics::MockEventSender::new();
1308                stats_event_sender_mock
1309                    .expect_send_event()
1310                    .with(eq(statistics::Event::Udp4Scrape))
1311                    .times(1)
1312                    .returning(|_| Box::pin(future::ready(Some(Ok(())))));
1313                let stats_event_sender = Box::new(stats_event_sender_mock);
1314
1315                let remote_addr = sample_ipv4_remote_addr();
1316                let tracker = Arc::new(
1317                    core::Tracker::new(
1318                        &tracker_configuration().core,
1319                        Some(stats_event_sender),
1320                        statistics::Repo::new(),
1321                    )
1322                    .unwrap(),
1323                );
1324
1325                handle_scrape(remote_addr, &sample_scrape_request(&remote_addr), &tracker)
1326                    .await
1327                    .unwrap();
1328            }
1329        }
1330
1331        mod using_ipv6 {
1332            use std::future;
1333            use std::sync::Arc;
1334
1335            use mockall::predicate::eq;
1336
1337            use super::sample_scrape_request;
1338            use crate::core::{self, statistics};
1339            use crate::servers::udp::handlers::handle_scrape;
1340            use crate::servers::udp::handlers::tests::{sample_ipv6_remote_addr, tracker_configuration};
1341
1342            #[tokio::test]
1343            async fn should_send_the_upd6_scrape_event() {
1344                let mut stats_event_sender_mock = statistics::MockEventSender::new();
1345                stats_event_sender_mock
1346                    .expect_send_event()
1347                    .with(eq(statistics::Event::Udp6Scrape))
1348                    .times(1)
1349                    .returning(|_| Box::pin(future::ready(Some(Ok(())))));
1350                let stats_event_sender = Box::new(stats_event_sender_mock);
1351
1352                let remote_addr = sample_ipv6_remote_addr();
1353                let tracker = Arc::new(
1354                    core::Tracker::new(
1355                        &tracker_configuration().core,
1356                        Some(stats_event_sender),
1357                        statistics::Repo::new(),
1358                    )
1359                    .unwrap(),
1360                );
1361
1362                handle_scrape(remote_addr, &sample_scrape_request(&remote_addr), &tracker)
1363                    .await
1364                    .unwrap();
1365            }
1366        }
1367    }
1368}