@@ -448,6 +448,7 @@ pub mod torrent;
448
448
449
449
pub mod peer_tests;
450
450
451
+ use std:: cmp:: max;
451
452
use std:: collections:: HashMap ;
452
453
use std:: net:: IpAddr ;
453
454
use std:: panic:: Location ;
@@ -520,6 +521,48 @@ pub struct AnnounceData {
520
521
pub policy : AnnouncePolicy ,
521
522
}
522
523
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
+
523
566
/// Structure that holds the data returned by the `scrape` request.
524
567
#[ derive( Debug , PartialEq , Default ) ]
525
568
pub struct ScrapeData {
@@ -639,7 +682,13 @@ impl Tracker {
639
682
/// # Context: Tracker
640
683
///
641
684
/// 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 {
643
692
// code-review: maybe instead of mutating the peer we could just return
644
693
// a tuple with the new peer and the announce data: (Peer, AnnounceData).
645
694
// It could even be a different struct: `StoredPeer` or `PublicPeer`.
@@ -661,7 +710,7 @@ impl Tracker {
661
710
662
711
let stats = self . upsert_peer_and_get_stats ( info_hash, peer) ;
663
712
664
- let peers = self . get_peers_for ( info_hash, peer) ;
713
+ let peers = self . get_peers_for ( info_hash, peer, peers_wanted . limit ( ) ) ;
665
714
666
715
AnnounceData {
667
716
peers,
@@ -713,16 +762,21 @@ impl Tracker {
713
762
Ok ( ( ) )
714
763
}
715
764
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 > > {
717
771
match self . torrents . get ( info_hash) {
718
772
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 ) ) ) ,
720
774
}
721
775
}
722
776
723
777
/// # Context: Tracker
724
778
///
725
- /// Get all torrent peers for a given torrent
779
+ /// Get torrent peers for a given torrent.
726
780
pub fn get_torrent_peers ( & self , info_hash : & InfoHash ) -> Vec < Arc < peer:: Peer > > {
727
781
match self . torrents . get ( info_hash) {
728
782
None => vec ! [ ] ,
@@ -1199,6 +1253,7 @@ mod tests {
1199
1253
use std:: sync:: Arc ;
1200
1254
1201
1255
use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes , PeerId } ;
1256
+ use torrust_tracker_configuration:: TORRENT_PEERS_LIMIT ;
1202
1257
use torrust_tracker_primitives:: info_hash:: InfoHash ;
1203
1258
use torrust_tracker_primitives:: DurationSinceUnixEpoch ;
1204
1259
use torrust_tracker_test_helpers:: configuration;
@@ -1328,7 +1383,7 @@ mod tests {
1328
1383
}
1329
1384
1330
1385
#[ 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 ( ) {
1332
1387
let tracker = public_tracker ( ) ;
1333
1388
1334
1389
let info_hash = sample_info_hash ( ) ;
@@ -1341,20 +1396,93 @@ mod tests {
1341
1396
assert_eq ! ( peers, vec![ Arc :: new( peer) ] ) ;
1342
1397
}
1343
1398
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
+
1344
1442
#[ 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 ( ) {
1346
1444
let tracker = public_tracker ( ) ;
1347
1445
1348
1446
let info_hash = sample_info_hash ( ) ;
1349
1447
let peer = sample_peer ( ) ;
1350
1448
1351
1449
tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1352
1450
1353
- let peers = tracker. get_peers_for ( & info_hash, & peer) ;
1451
+ let peers = tracker. get_peers_for ( & info_hash, & peer, TORRENT_PEERS_LIMIT ) ;
1354
1452
1355
1453
assert_eq ! ( peers, vec![ ] ) ;
1356
1454
}
1357
1455
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
+
1358
1486
#[ tokio:: test]
1359
1487
async fn it_should_return_the_torrent_metrics ( ) {
1360
1488
let tracker = public_tracker ( ) ;
@@ -1409,6 +1537,7 @@ mod tests {
1409
1537
use crate :: core:: tests:: the_tracker:: {
1410
1538
peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
1411
1539
} ;
1540
+ use crate :: core:: PeersWanted ;
1412
1541
1413
1542
mod should_assign_the_ip_to_the_peer {
1414
1543
@@ -1514,7 +1643,7 @@ mod tests {
1514
1643
1515
1644
let mut peer = sample_peer ( ) ;
1516
1645
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 ) ;
1518
1647
1519
1648
assert_eq ! ( announce_data. peers, vec![ ] ) ;
1520
1649
}
@@ -1524,10 +1653,15 @@ mod tests {
1524
1653
let tracker = public_tracker ( ) ;
1525
1654
1526
1655
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
+ ) ;
1528
1662
1529
1663
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 ) ;
1531
1665
1532
1666
assert_eq ! ( announce_data. peers, vec![ Arc :: new( previously_announced_peer) ] ) ;
1533
1667
}
@@ -1537,14 +1671,15 @@ mod tests {
1537
1671
use crate :: core:: tests:: the_tracker:: {
1538
1672
completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
1539
1673
} ;
1674
+ use crate :: core:: PeersWanted ;
1540
1675
1541
1676
#[ tokio:: test]
1542
1677
async fn when_the_peer_is_a_seeder ( ) {
1543
1678
let tracker = public_tracker ( ) ;
1544
1679
1545
1680
let mut peer = seeder ( ) ;
1546
1681
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 ) ;
1548
1683
1549
1684
assert_eq ! ( announce_data. stats. complete, 1 ) ;
1550
1685
}
@@ -1555,7 +1690,7 @@ mod tests {
1555
1690
1556
1691
let mut peer = leecher ( ) ;
1557
1692
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 ) ;
1559
1694
1560
1695
assert_eq ! ( announce_data. stats. incomplete, 1 ) ;
1561
1696
}
@@ -1566,10 +1701,11 @@ mod tests {
1566
1701
1567
1702
// We have to announce with "started" event because peer does not count if peer was not previously known
1568
1703
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 ) ;
1570
1705
1571
1706
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 ) ;
1573
1709
1574
1710
assert_eq ! ( announce_data. stats. downloaded, 1 ) ;
1575
1711
}
@@ -1583,7 +1719,7 @@ mod tests {
1583
1719
use torrust_tracker_primitives:: info_hash:: InfoHash ;
1584
1720
1585
1721
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 } ;
1587
1723
1588
1724
#[ tokio:: test]
1589
1725
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 {
1609
1745
1610
1746
// Announce a "complete" peer for the torrent
1611
1747
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
+ ) ;
1613
1754
1614
1755
// Announce an "incomplete" peer for the torrent
1615
1756
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
+ ) ;
1617
1763
1618
1764
// Scrape
1619
1765
let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
@@ -1740,7 +1886,7 @@ mod tests {
1740
1886
use crate :: core:: tests:: the_tracker:: {
1741
1887
complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
1742
1888
} ;
1743
- use crate :: core:: ScrapeData ;
1889
+ use crate :: core:: { PeersWanted , ScrapeData } ;
1744
1890
1745
1891
#[ test]
1746
1892
fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes ( ) {
@@ -1761,11 +1907,11 @@ mod tests {
1761
1907
let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0" . parse :: < InfoHash > ( ) . unwrap ( ) ;
1762
1908
1763
1909
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 ) ;
1765
1911
1766
1912
// Announce twice to force non zeroed swarm metadata
1767
1913
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 ) ;
1769
1915
1770
1916
let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
1771
1917
0 commit comments