@@ -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,38 @@ 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+ fn limit ( & self ) -> usize {
536+ match self {
537+ PeersWanted :: All => TORRENT_PEERS_LIMIT ,
538+ PeersWanted :: Only { amount } => * amount,
539+ }
540+ }
541+ }
542+
543+ impl From < i32 > for PeersWanted {
544+ fn from ( value : i32 ) -> Self {
545+ if value > 0 {
546+ match value. try_into ( ) {
547+ Ok ( peers_wanted) => Self :: Only { amount : peers_wanted } ,
548+ Err ( _) => Self :: All ,
549+ }
550+ } else {
551+ Self :: All
552+ }
553+ }
554+ }
555+
523556/// Structure that holds the data returned by the `scrape` request.
524557#[ derive( Debug , PartialEq , Default ) ]
525558pub struct ScrapeData {
@@ -639,7 +672,13 @@ impl Tracker {
639672 /// # Context: Tracker
640673 ///
641674 /// 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 {
675+ pub fn announce (
676+ & self ,
677+ info_hash : & InfoHash ,
678+ peer : & mut peer:: Peer ,
679+ remote_client_ip : & IpAddr ,
680+ peers_wanted : & PeersWanted ,
681+ ) -> AnnounceData {
643682 // code-review: maybe instead of mutating the peer we could just return
644683 // a tuple with the new peer and the announce data: (Peer, AnnounceData).
645684 // It could even be a different struct: `StoredPeer` or `PublicPeer`.
@@ -661,7 +700,7 @@ impl Tracker {
661700
662701 let stats = self . upsert_peer_and_get_stats ( info_hash, peer) ;
663702
664- let peers = self . get_peers_for ( info_hash, peer) ;
703+ let peers = self . get_peers_for ( info_hash, peer, peers_wanted . limit ( ) ) ;
665704
666705 AnnounceData {
667706 peers,
@@ -713,16 +752,21 @@ impl Tracker {
713752 Ok ( ( ) )
714753 }
715754
716- fn get_peers_for ( & self , info_hash : & InfoHash , peer : & peer:: Peer ) -> Vec < Arc < peer:: Peer > > {
755+ /// # Context: Tracker
756+ ///
757+ /// Get torrent peers for a given torrent and client.
758+ ///
759+ /// It filters out the client making the request.
760+ fn get_peers_for ( & self , info_hash : & InfoHash , peer : & peer:: Peer , limit : usize ) -> Vec < Arc < peer:: Peer > > {
717761 match self . torrents . get ( info_hash) {
718762 None => vec ! [ ] ,
719- Some ( entry) => entry. get_peers_for_client ( & peer. peer_addr , Some ( TORRENT_PEERS_LIMIT ) ) ,
763+ Some ( entry) => entry. get_peers_for_client ( & peer. peer_addr , Some ( max ( limit , TORRENT_PEERS_LIMIT ) ) ) ,
720764 }
721765 }
722766
723767 /// # Context: Tracker
724768 ///
725- /// Get all torrent peers for a given torrent
769+ /// Get torrent peers for a given torrent.
726770 pub fn get_torrent_peers ( & self , info_hash : & InfoHash ) -> Vec < Arc < peer:: Peer > > {
727771 match self . torrents . get ( info_hash) {
728772 None => vec ! [ ] ,
@@ -1199,6 +1243,7 @@ mod tests {
11991243 use std:: sync:: Arc ;
12001244
12011245 use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes , PeerId } ;
1246+ use torrust_tracker_configuration:: TORRENT_PEERS_LIMIT ;
12021247 use torrust_tracker_primitives:: info_hash:: InfoHash ;
12031248 use torrust_tracker_primitives:: DurationSinceUnixEpoch ;
12041249 use torrust_tracker_test_helpers:: configuration;
@@ -1328,7 +1373,7 @@ mod tests {
13281373 }
13291374
13301375 #[ tokio:: test]
1331- async fn it_should_return_all_the_peers_for_a_given_torrent ( ) {
1376+ async fn it_should_return_the_peers_for_a_given_torrent ( ) {
13321377 let tracker = public_tracker ( ) ;
13331378
13341379 let info_hash = sample_info_hash ( ) ;
@@ -1341,20 +1386,93 @@ mod tests {
13411386 assert_eq ! ( peers, vec![ Arc :: new( peer) ] ) ;
13421387 }
13431388
1389+ /// It generates a peer id from a number where the number is the last
1390+ /// part of the peer ID. For example, for `12` it returns
1391+ /// `-qB00000000000000012`.
1392+ fn numeric_peer_id ( two_digits_value : i32 ) -> PeerId {
1393+ // Format idx as a string with leading zeros, ensuring it has exactly 2 digits
1394+ let idx_str = format ! ( "{two_digits_value:02}" ) ;
1395+
1396+ // Create the base part of the peer ID.
1397+ let base = b"-qB00000000000000000" ;
1398+
1399+ // Concatenate the base with idx bytes, ensuring the total length is 20 bytes.
1400+ let mut peer_id_bytes = [ 0u8 ; 20 ] ;
1401+ peer_id_bytes[ ..base. len ( ) ] . copy_from_slice ( base) ;
1402+ peer_id_bytes[ base. len ( ) - idx_str. len ( ) ..] . copy_from_slice ( idx_str. as_bytes ( ) ) ;
1403+
1404+ PeerId ( peer_id_bytes)
1405+ }
1406+
13441407 #[ tokio:: test]
1345- async fn it_should_return_all_the_peers_for_a_given_torrent_excluding_a_given_peer ( ) {
1408+ async fn it_should_return_74_peers_at_the_most_for_a_given_torrent ( ) {
1409+ let tracker = public_tracker ( ) ;
1410+
1411+ let info_hash = sample_info_hash ( ) ;
1412+
1413+ for idx in 1 ..=75 {
1414+ let peer = Peer {
1415+ peer_id : numeric_peer_id ( idx) ,
1416+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , idx. try_into ( ) . unwrap ( ) ) ) , 8080 ) ,
1417+ updated : DurationSinceUnixEpoch :: new ( 1_669_397_478_934 , 0 ) ,
1418+ uploaded : NumberOfBytes :: new ( 0 ) ,
1419+ downloaded : NumberOfBytes :: new ( 0 ) ,
1420+ left : NumberOfBytes :: new ( 0 ) , // No bytes left to download
1421+ event : AnnounceEvent :: Completed ,
1422+ } ;
1423+
1424+ tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1425+ }
1426+
1427+ let peers = tracker. get_torrent_peers ( & info_hash) ;
1428+
1429+ assert_eq ! ( peers. len( ) , 74 ) ;
1430+ }
1431+
1432+ #[ tokio:: test]
1433+ async fn it_should_return_the_peers_for_a_given_torrent_excluding_a_given_peer ( ) {
13461434 let tracker = public_tracker ( ) ;
13471435
13481436 let info_hash = sample_info_hash ( ) ;
13491437 let peer = sample_peer ( ) ;
13501438
13511439 tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
13521440
1353- let peers = tracker. get_peers_for ( & info_hash, & peer) ;
1441+ let peers = tracker. get_peers_for ( & info_hash, & peer, TORRENT_PEERS_LIMIT ) ;
13541442
13551443 assert_eq ! ( peers, vec![ ] ) ;
13561444 }
13571445
1446+ #[ tokio:: test]
1447+ async fn it_should_return_74_peers_at_the_most_for_a_given_torrent_when_it_filters_out_a_given_peer ( ) {
1448+ let tracker = public_tracker ( ) ;
1449+
1450+ let info_hash = sample_info_hash ( ) ;
1451+
1452+ let excluded_peer = sample_peer ( ) ;
1453+
1454+ tracker. upsert_peer_and_get_stats ( & info_hash, & excluded_peer) ;
1455+
1456+ // Add 74 peers
1457+ for idx in 2 ..=75 {
1458+ let peer = Peer {
1459+ peer_id : numeric_peer_id ( idx) ,
1460+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , idx. try_into ( ) . unwrap ( ) ) ) , 8080 ) ,
1461+ updated : DurationSinceUnixEpoch :: new ( 1_669_397_478_934 , 0 ) ,
1462+ uploaded : NumberOfBytes :: new ( 0 ) ,
1463+ downloaded : NumberOfBytes :: new ( 0 ) ,
1464+ left : NumberOfBytes :: new ( 0 ) , // No bytes left to download
1465+ event : AnnounceEvent :: Completed ,
1466+ } ;
1467+
1468+ tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1469+ }
1470+
1471+ let peers = tracker. get_peers_for ( & info_hash, & excluded_peer, TORRENT_PEERS_LIMIT ) ;
1472+
1473+ assert_eq ! ( peers. len( ) , 74 ) ;
1474+ }
1475+
13581476 #[ tokio:: test]
13591477 async fn it_should_return_the_torrent_metrics ( ) {
13601478 let tracker = public_tracker ( ) ;
@@ -1409,6 +1527,7 @@ mod tests {
14091527 use crate :: core:: tests:: the_tracker:: {
14101528 peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
14111529 } ;
1530+ use crate :: core:: PeersWanted ;
14121531
14131532 mod should_assign_the_ip_to_the_peer {
14141533
@@ -1514,7 +1633,7 @@ mod tests {
15141633
15151634 let mut peer = sample_peer ( ) ;
15161635
1517- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1636+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15181637
15191638 assert_eq ! ( announce_data. peers, vec![ ] ) ;
15201639 }
@@ -1524,10 +1643,15 @@ mod tests {
15241643 let tracker = public_tracker ( ) ;
15251644
15261645 let mut previously_announced_peer = sample_peer_1 ( ) ;
1527- tracker. announce ( & sample_info_hash ( ) , & mut previously_announced_peer, & peer_ip ( ) ) ;
1646+ tracker. announce (
1647+ & sample_info_hash ( ) ,
1648+ & mut previously_announced_peer,
1649+ & peer_ip ( ) ,
1650+ & PeersWanted :: All ,
1651+ ) ;
15281652
15291653 let mut peer = sample_peer_2 ( ) ;
1530- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1654+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15311655
15321656 assert_eq ! ( announce_data. peers, vec![ Arc :: new( previously_announced_peer) ] ) ;
15331657 }
@@ -1537,14 +1661,15 @@ mod tests {
15371661 use crate :: core:: tests:: the_tracker:: {
15381662 completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
15391663 } ;
1664+ use crate :: core:: PeersWanted ;
15401665
15411666 #[ tokio:: test]
15421667 async fn when_the_peer_is_a_seeder ( ) {
15431668 let tracker = public_tracker ( ) ;
15441669
15451670 let mut peer = seeder ( ) ;
15461671
1547- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1672+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15481673
15491674 assert_eq ! ( announce_data. stats. complete, 1 ) ;
15501675 }
@@ -1555,7 +1680,7 @@ mod tests {
15551680
15561681 let mut peer = leecher ( ) ;
15571682
1558- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) ) ;
1683+ let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15591684
15601685 assert_eq ! ( announce_data. stats. incomplete, 1 ) ;
15611686 }
@@ -1566,10 +1691,11 @@ mod tests {
15661691
15671692 // We have to announce with "started" event because peer does not count if peer was not previously known
15681693 let mut started_peer = started_peer ( ) ;
1569- tracker. announce ( & sample_info_hash ( ) , & mut started_peer, & peer_ip ( ) ) ;
1694+ tracker. announce ( & sample_info_hash ( ) , & mut started_peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15701695
15711696 let mut completed_peer = completed_peer ( ) ;
1572- let announce_data = tracker. announce ( & sample_info_hash ( ) , & mut completed_peer, & peer_ip ( ) ) ;
1697+ let announce_data =
1698+ tracker. announce ( & sample_info_hash ( ) , & mut completed_peer, & peer_ip ( ) , & PeersWanted :: All ) ;
15731699
15741700 assert_eq ! ( announce_data. stats. downloaded, 1 ) ;
15751701 }
@@ -1583,7 +1709,7 @@ mod tests {
15831709 use torrust_tracker_primitives:: info_hash:: InfoHash ;
15841710
15851711 use crate :: core:: tests:: the_tracker:: { complete_peer, incomplete_peer, public_tracker} ;
1586- use crate :: core:: { ScrapeData , SwarmMetadata } ;
1712+ use crate :: core:: { PeersWanted , ScrapeData , SwarmMetadata } ;
15871713
15881714 #[ tokio:: test]
15891715 async fn it_should_return_a_zeroed_swarm_metadata_for_the_requested_file_if_the_tracker_does_not_have_that_torrent (
@@ -1609,11 +1735,21 @@ mod tests {
16091735
16101736 // Announce a "complete" peer for the torrent
16111737 let mut complete_peer = complete_peer ( ) ;
1612- tracker. announce ( & info_hash, & mut complete_peer, & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 10 ) ) ) ;
1738+ tracker. announce (
1739+ & info_hash,
1740+ & mut complete_peer,
1741+ & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 10 ) ) ,
1742+ & PeersWanted :: All ,
1743+ ) ;
16131744
16141745 // Announce an "incomplete" peer for the torrent
16151746 let mut incomplete_peer = incomplete_peer ( ) ;
1616- tracker. announce ( & info_hash, & mut incomplete_peer, & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 11 ) ) ) ;
1747+ tracker. announce (
1748+ & info_hash,
1749+ & mut incomplete_peer,
1750+ & IpAddr :: V4 ( Ipv4Addr :: new ( 126 , 0 , 0 , 11 ) ) ,
1751+ & PeersWanted :: All ,
1752+ ) ;
16171753
16181754 // Scrape
16191755 let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
@@ -1740,7 +1876,7 @@ mod tests {
17401876 use crate :: core:: tests:: the_tracker:: {
17411877 complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
17421878 } ;
1743- use crate :: core:: ScrapeData ;
1879+ use crate :: core:: { PeersWanted , ScrapeData } ;
17441880
17451881 #[ test]
17461882 fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes ( ) {
@@ -1761,11 +1897,11 @@ mod tests {
17611897 let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0" . parse :: < InfoHash > ( ) . unwrap ( ) ;
17621898
17631899 let mut peer = incomplete_peer ( ) ;
1764- tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) ) ;
1900+ tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
17651901
17661902 // Announce twice to force non zeroed swarm metadata
17671903 let mut peer = complete_peer ( ) ;
1768- tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) ) ;
1904+ tracker. announce ( & info_hash, & mut peer, & peer_ip ( ) , & PeersWanted :: All ) ;
17691905
17701906 let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
17711907
0 commit comments