@@ -448,6 +448,7 @@ pub mod torrent;
448448
449449pub mod peer_tests;
450450
451+ use std:: cmp:: max;
451452use std:: collections:: HashMap ;
452453use std:: net:: IpAddr ;
453454use std:: panic:: Location ;
@@ -520,6 +521,48 @@ pub struct AnnounceData {
520521 pub policy : AnnouncePolicy ,
521522}
522523
524+ /// How many peers the peer announcing wants in the announce response.
525+ #[ derive( Clone , Debug , PartialEq , Default ) ]
526+ pub enum PeersWanted {
527+ /// The peer wants as many peers as possible in the announce response.
528+ #[ default]
529+ All ,
530+ /// The peer only wants a certain amount of peers in the announce response.
531+ Only { amount : usize } ,
532+ }
533+
534+ impl PeersWanted {
535+ #[ must_use]
536+ pub fn only ( limit : u32 ) -> Self {
537+ let amount: usize = match limit. try_into ( ) {
538+ Ok ( amount) => amount,
539+ Err ( _) => TORRENT_PEERS_LIMIT ,
540+ } ;
541+
542+ Self :: Only { amount }
543+ }
544+
545+ fn limit ( & self ) -> usize {
546+ match self {
547+ PeersWanted :: All => TORRENT_PEERS_LIMIT ,
548+ PeersWanted :: Only { amount } => * amount,
549+ }
550+ }
551+ }
552+
553+ impl From < i32 > for PeersWanted {
554+ fn from ( value : i32 ) -> Self {
555+ if value > 0 {
556+ match value. try_into ( ) {
557+ Ok ( peers_wanted) => Self :: Only { amount : peers_wanted } ,
558+ Err ( _) => Self :: All ,
559+ }
560+ } else {
561+ Self :: All
562+ }
563+ }
564+ }
565+
523566/// Structure that holds the data returned by the `scrape` request.
524567#[ derive( Debug , PartialEq , Default ) ]
525568pub struct ScrapeData {
@@ -639,7 +682,13 @@ impl Tracker {
639682 /// # Context: Tracker
640683 ///
641684 /// BEP 03: [The `BitTorrent` Protocol Specification](https://www.bittorrent.org/beps/bep_0003.html).
642- pub fn announce ( & self , info_hash : & InfoHash , peer : & mut peer:: Peer , remote_client_ip : & IpAddr ) -> AnnounceData {
685+ pub fn announce (
686+ & self ,
687+ info_hash : & InfoHash ,
688+ peer : & mut peer:: Peer ,
689+ remote_client_ip : & IpAddr ,
690+ peers_wanted : & PeersWanted ,
691+ ) -> AnnounceData {
643692 // code-review: maybe instead of mutating the peer we could just return
644693 // a tuple with the new peer and the announce data: (Peer, AnnounceData).
645694 // It could even be a different struct: `StoredPeer` or `PublicPeer`.
@@ -661,7 +710,7 @@ impl Tracker {
661710
662711 let stats = self . upsert_peer_and_get_stats ( info_hash, peer) ;
663712
664- let peers = self . get_peers_for ( info_hash, peer) ;
713+ let peers = self . get_peers_for ( info_hash, peer, peers_wanted . limit ( ) ) ;
665714
666715 AnnounceData {
667716 peers,
@@ -713,16 +762,21 @@ impl Tracker {
713762 Ok ( ( ) )
714763 }
715764
716- fn get_peers_for ( & self , info_hash : & InfoHash , peer : & peer:: Peer ) -> Vec < Arc < peer:: Peer > > {
765+ /// # Context: Tracker
766+ ///
767+ /// Get torrent peers for a given torrent and client.
768+ ///
769+ /// It filters out the client making the request.
770+ fn get_peers_for ( & self , info_hash : & InfoHash , peer : & peer:: Peer , limit : usize ) -> Vec < Arc < peer:: Peer > > {
717771 match self . torrents . get ( info_hash) {
718772 None => vec ! [ ] ,
719- Some ( entry) => entry. get_peers_for_client ( & peer. peer_addr , Some ( TORRENT_PEERS_LIMIT ) ) ,
773+ Some ( entry) => entry. get_peers_for_client ( & peer. peer_addr , Some ( max ( limit , TORRENT_PEERS_LIMIT ) ) ) ,
720774 }
721775 }
722776
723777 /// # Context: Tracker
724778 ///
725- /// Get all torrent peers for a given torrent
779+ /// Get torrent peers for a given torrent.
726780 pub fn get_torrent_peers ( & self , info_hash : & InfoHash ) -> Vec < Arc < peer:: Peer > > {
727781 match self . torrents . get ( info_hash) {
728782 None => vec ! [ ] ,
@@ -1199,6 +1253,7 @@ mod tests {
11991253 use std:: sync:: Arc ;
12001254
12011255 use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes , PeerId } ;
1256+ use torrust_tracker_configuration:: TORRENT_PEERS_LIMIT ;
12021257 use torrust_tracker_primitives:: info_hash:: InfoHash ;
12031258 use torrust_tracker_primitives:: DurationSinceUnixEpoch ;
12041259 use torrust_tracker_test_helpers:: configuration;
@@ -1328,7 +1383,7 @@ mod tests {
13281383 }
13291384
13301385 #[ tokio:: test]
1331- async fn it_should_return_all_the_peers_for_a_given_torrent ( ) {
1386+ async fn it_should_return_the_peers_for_a_given_torrent ( ) {
13321387 let tracker = public_tracker ( ) ;
13331388
13341389 let info_hash = sample_info_hash ( ) ;
@@ -1341,20 +1396,93 @@ mod tests {
13411396 assert_eq ! ( peers, vec![ Arc :: new( peer) ] ) ;
13421397 }
13431398
1399+ /// It generates a peer id from a number where the number is the last
1400+ /// part of the peer ID. For example, for `12` it returns
1401+ /// `-qB00000000000000012`.
1402+ fn numeric_peer_id ( two_digits_value : i32 ) -> PeerId {
1403+ // Format idx as a string with leading zeros, ensuring it has exactly 2 digits
1404+ let idx_str = format ! ( "{two_digits_value:02}" ) ;
1405+
1406+ // Create the base part of the peer ID.
1407+ let base = b"-qB00000000000000000" ;
1408+
1409+ // Concatenate the base with idx bytes, ensuring the total length is 20 bytes.
1410+ let mut peer_id_bytes = [ 0u8 ; 20 ] ;
1411+ peer_id_bytes[ ..base. len ( ) ] . copy_from_slice ( base) ;
1412+ peer_id_bytes[ base. len ( ) - idx_str. len ( ) ..] . copy_from_slice ( idx_str. as_bytes ( ) ) ;
1413+
1414+ PeerId ( peer_id_bytes)
1415+ }
1416+
1417+ #[ tokio:: test]
1418+ async fn it_should_return_74_peers_at_the_most_for_a_given_torrent ( ) {
1419+ let tracker = public_tracker ( ) ;
1420+
1421+ let info_hash = sample_info_hash ( ) ;
1422+
1423+ for idx in 1 ..=75 {
1424+ let peer = Peer {
1425+ peer_id : numeric_peer_id ( idx) ,
1426+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , idx. try_into ( ) . unwrap ( ) ) ) , 8080 ) ,
1427+ updated : DurationSinceUnixEpoch :: new ( 1_669_397_478_934 , 0 ) ,
1428+ uploaded : NumberOfBytes :: new ( 0 ) ,
1429+ downloaded : NumberOfBytes :: new ( 0 ) ,
1430+ left : NumberOfBytes :: new ( 0 ) , // No bytes left to download
1431+ event : AnnounceEvent :: Completed ,
1432+ } ;
1433+
1434+ tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1435+ }
1436+
1437+ let peers = tracker. get_torrent_peers ( & info_hash) ;
1438+
1439+ assert_eq ! ( peers. len( ) , 74 ) ;
1440+ }
1441+
13441442 #[ tokio:: test]
1345- async fn it_should_return_all_the_peers_for_a_given_torrent_excluding_a_given_peer ( ) {
1443+ async fn it_should_return_the_peers_for_a_given_torrent_excluding_a_given_peer ( ) {
13461444 let tracker = public_tracker ( ) ;
13471445
13481446 let info_hash = sample_info_hash ( ) ;
13491447 let peer = sample_peer ( ) ;
13501448
13511449 tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
13521450
1353- let peers = tracker. get_peers_for ( & info_hash, & peer) ;
1451+ let peers = tracker. get_peers_for ( & info_hash, & peer, TORRENT_PEERS_LIMIT ) ;
13541452
13551453 assert_eq ! ( peers, vec![ ] ) ;
13561454 }
13571455
1456+ #[ tokio:: test]
1457+ async fn it_should_return_74_peers_at_the_most_for_a_given_torrent_when_it_filters_out_a_given_peer ( ) {
1458+ let tracker = public_tracker ( ) ;
1459+
1460+ let info_hash = sample_info_hash ( ) ;
1461+
1462+ let excluded_peer = sample_peer ( ) ;
1463+
1464+ tracker. upsert_peer_and_get_stats ( & info_hash, & excluded_peer) ;
1465+
1466+ // Add 74 peers
1467+ for idx in 2 ..=75 {
1468+ let peer = Peer {
1469+ peer_id : numeric_peer_id ( idx) ,
1470+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , idx. try_into ( ) . unwrap ( ) ) ) , 8080 ) ,
1471+ updated : DurationSinceUnixEpoch :: new ( 1_669_397_478_934 , 0 ) ,
1472+ uploaded : NumberOfBytes :: new ( 0 ) ,
1473+ downloaded : NumberOfBytes :: new ( 0 ) ,
1474+ left : NumberOfBytes :: new ( 0 ) , // No bytes left to download
1475+ event : AnnounceEvent :: Completed ,
1476+ } ;
1477+
1478+ tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1479+ }
1480+
1481+ let peers = tracker. get_peers_for ( & info_hash, & excluded_peer, TORRENT_PEERS_LIMIT ) ;
1482+
1483+ assert_eq ! ( peers. len( ) , 74 ) ;
1484+ }
1485+
13581486 #[ tokio:: test]
13591487 async fn it_should_return_the_torrent_metrics ( ) {
13601488 let tracker = public_tracker ( ) ;
@@ -1409,6 +1537,7 @@ mod tests {
14091537 use crate :: core:: tests:: the_tracker:: {
14101538 peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
14111539 } ;
1540+ use crate :: core:: PeersWanted ;
14121541
14131542 mod should_assign_the_ip_to_the_peer {
14141543
@@ -1514,7 +1643,7 @@ mod tests {
15141643
15151644 let mut peer = sample_peer ( ) ;
15161645
1517- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1646+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15181647
15191648 assert_eq ! ( announce_data. peers, vec![ ] ) ;
15201649 }
@@ -1524,10 +1653,15 @@ mod tests {
15241653 let tracker = public_tracker ( ) ;
15251654
15261655 let mut previously_announced_peer = sample_peer_1 ( ) ;
1527- tracker. announce ( & sample_info_hash ( ) , & mut previously_announced_peer, & peer_ip ( ) ) ;
1656+ tracker. announce (
1657+ & sample_info_hash ( ) ,
1658+ & mut previously_announced_peer,
1659+ & peer_ip ( ) ,
1660+ & PeersWanted :: All ,
1661+ ) ;
15281662
15291663 let mut peer = sample_peer_2 ( ) ;
1530- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1664+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15311665
15321666 assert_eq ! ( announce_data. peers, vec![ Arc :: new( previously_announced_peer) ] ) ;
15331667 }
@@ -1537,14 +1671,15 @@ mod tests {
15371671 use crate :: core:: tests:: the_tracker:: {
15381672 completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
15391673 } ;
1674+ use crate :: core:: PeersWanted ;
15401675
15411676 #[ tokio:: test]
15421677 async fn when_the_peer_is_a_seeder ( ) {
15431678 let tracker = public_tracker ( ) ;
15441679
15451680 let mut peer = seeder ( ) ;
15461681
1547- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1682+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15481683
15491684 assert_eq ! ( announce_data. stats. complete, 1 ) ;
15501685 }
@@ -1555,7 +1690,7 @@ mod tests {
15551690
15561691 let mut peer = leecher ( ) ;
15571692
1558- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1693+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15591694
15601695 assert_eq ! ( announce_data. stats. incomplete, 1 ) ;
15611696 }
@@ -1566,10 +1701,11 @@ mod tests {
15661701
15671702 // We have to announce with "started" event because peer does not count if peer was not previously known
15681703 let mut started_peer = started_peer ( ) ;
1569- tracker. announce ( & sample_info_hash ( ) , & mut started_peer, & peer_ip ( ) ) ;
1704+ tracker. announce ( & sample_info_hash ( ) , & mut started_peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15701705
15711706 let mut completed_peer = completed_peer ( ) ;
1572- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut completed_peer, & peer_ip ( ) ) ;
1707+ let announce_data =
1708+ tracker. announce ( & sample_info_hash ( ) , & mut completed_peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15731709
15741710 assert_eq ! ( announce_data. stats. downloaded, 1 ) ;
15751711 }
@@ -1583,7 +1719,7 @@ mod tests {
15831719 use torrust_tracker_primitives:: info_hash:: InfoHash ;
15841720
15851721 use crate :: core:: tests:: the_tracker:: { complete_peer, incomplete_peer, public_tracker} ;
1586- use crate :: core:: { ScrapeData , SwarmMetadata } ;
1722+ use crate :: core:: { PeersWanted , ScrapeData , SwarmMetadata } ;
15871723
15881724 #[ tokio:: test]
15891725 async fn it_should_return_a_zeroed_swarm_metadata_for_the_requested_file_if_the_tracker_does_not_have_that_torrent (
@@ -1609,11 +1745,21 @@ mod tests {
16091745
16101746 // Announce a "complete" peer for the torrent
16111747 let mut complete_peer = complete_peer ( ) ;
1612- tracker. announce ( & info_hash, & mut complete_peer, & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 10 ) ) ) ;
1748+ tracker. announce (
1749+ & info_hash,
1750+ & mut complete_peer,
1751+ & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 10 ) ) ,
1752+ & PeersWanted :: All ,
1753+ ) ;
16131754
16141755 // Announce an "incomplete" peer for the torrent
16151756 let mut incomplete_peer = incomplete_peer ( ) ;
1616- tracker. announce ( & info_hash, & mut incomplete_peer, & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 11 ) ) ) ;
1757+ tracker. announce (
1758+ & info_hash,
1759+ & mut incomplete_peer,
1760+ & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 11 ) ) ,
1761+ & PeersWanted :: All ,
1762+ ) ;
16171763
16181764 // Scrape
16191765 let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
@@ -1740,7 +1886,7 @@ mod tests {
17401886 use crate :: core:: tests:: the_tracker:: {
17411887 complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
17421888 } ;
1743- use crate :: core:: ScrapeData ;
1889+ use crate :: core:: { PeersWanted , ScrapeData } ;
17441890
17451891 #[ test]
17461892 fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes ( ) {
@@ -1761,11 +1907,11 @@ mod tests {
17611907 let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0" . parse :: < InfoHash > ( ) . unwrap ( ) ;
17621908
17631909 let mut peer = incomplete_peer ( ) ;
1764- tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) ) ;
1910+ tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
17651911
17661912 // Announce twice to force non zeroed swarm metadata
17671913 let mut peer = complete_peer ( ) ;
1768- tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) ) ;
1914+ tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
17691915
17701916 let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
17711917
0 commit comments