@@ -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,38 @@ 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
+ 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
+
523
556
/// Structure that holds the data returned by the `scrape` request.
524
557
#[ derive( Debug , PartialEq , Default ) ]
525
558
pub struct ScrapeData {
@@ -639,7 +672,13 @@ impl Tracker {
639
672
/// # Context: Tracker
640
673
///
641
674
/// 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 {
643
682
// code-review: maybe instead of mutating the peer we could just return
644
683
// a tuple with the new peer and the announce data: (Peer, AnnounceData).
645
684
// It could even be a different struct: `StoredPeer` or `PublicPeer`.
@@ -661,7 +700,7 @@ impl Tracker {
661
700
662
701
let stats = self . upsert_peer_and_get_stats ( info_hash, peer) ;
663
702
664
- let peers = self . get_peers_for ( info_hash, peer) ;
703
+ let peers = self . get_peers_for ( info_hash, peer, peers_wanted . limit ( ) ) ;
665
704
666
705
AnnounceData {
667
706
peers,
@@ -713,16 +752,21 @@ impl Tracker {
713
752
Ok ( ( ) )
714
753
}
715
754
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 > > {
717
761
match self . torrents . get ( info_hash) {
718
762
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 ) ) ) ,
720
764
}
721
765
}
722
766
723
767
/// # Context: Tracker
724
768
///
725
- /// Get all torrent peers for a given torrent
769
+ /// Get torrent peers for a given torrent.
726
770
pub fn get_torrent_peers ( & self , info_hash : & InfoHash ) -> Vec < Arc < peer:: Peer > > {
727
771
match self . torrents . get ( info_hash) {
728
772
None => vec ! [ ] ,
@@ -1199,6 +1243,7 @@ mod tests {
1199
1243
use std:: sync:: Arc ;
1200
1244
1201
1245
use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes , PeerId } ;
1246
+ use torrust_tracker_configuration:: TORRENT_PEERS_LIMIT ;
1202
1247
use torrust_tracker_primitives:: info_hash:: InfoHash ;
1203
1248
use torrust_tracker_primitives:: DurationSinceUnixEpoch ;
1204
1249
use torrust_tracker_test_helpers:: configuration;
@@ -1328,7 +1373,7 @@ mod tests {
1328
1373
}
1329
1374
1330
1375
#[ 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 ( ) {
1332
1377
let tracker = public_tracker ( ) ;
1333
1378
1334
1379
let info_hash = sample_info_hash ( ) ;
@@ -1341,20 +1386,93 @@ mod tests {
1341
1386
assert_eq ! ( peers, vec![ Arc :: new( peer) ] ) ;
1342
1387
}
1343
1388
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
+
1344
1407
#[ 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 ( ) {
1346
1434
let tracker = public_tracker ( ) ;
1347
1435
1348
1436
let info_hash = sample_info_hash ( ) ;
1349
1437
let peer = sample_peer ( ) ;
1350
1438
1351
1439
tracker. upsert_peer_and_get_stats ( & info_hash, & peer) ;
1352
1440
1353
- let peers = tracker. get_peers_for ( & info_hash, & peer) ;
1441
+ let peers = tracker. get_peers_for ( & info_hash, & peer, TORRENT_PEERS_LIMIT ) ;
1354
1442
1355
1443
assert_eq ! ( peers, vec![ ] ) ;
1356
1444
}
1357
1445
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
+
1358
1476
#[ tokio:: test]
1359
1477
async fn it_should_return_the_torrent_metrics ( ) {
1360
1478
let tracker = public_tracker ( ) ;
@@ -1409,6 +1527,7 @@ mod tests {
1409
1527
use crate :: core:: tests:: the_tracker:: {
1410
1528
peer_ip, public_tracker, sample_info_hash, sample_peer, sample_peer_1, sample_peer_2,
1411
1529
} ;
1530
+ use crate :: core:: PeersWanted ;
1412
1531
1413
1532
mod should_assign_the_ip_to_the_peer {
1414
1533
@@ -1514,7 +1633,7 @@ mod tests {
1514
1633
1515
1634
let mut peer = sample_peer ( ) ;
1516
1635
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 ) ;
1518
1637
1519
1638
assert_eq ! ( announce_data. peers, vec![ ] ) ;
1520
1639
}
@@ -1524,10 +1643,15 @@ mod tests {
1524
1643
let tracker = public_tracker ( ) ;
1525
1644
1526
1645
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
+ ) ;
1528
1652
1529
1653
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 ) ;
1531
1655
1532
1656
assert_eq ! ( announce_data. peers, vec![ Arc :: new( previously_announced_peer) ] ) ;
1533
1657
}
@@ -1537,14 +1661,15 @@ mod tests {
1537
1661
use crate :: core:: tests:: the_tracker:: {
1538
1662
completed_peer, leecher, peer_ip, public_tracker, sample_info_hash, seeder, started_peer,
1539
1663
} ;
1664
+ use crate :: core:: PeersWanted ;
1540
1665
1541
1666
#[ tokio:: test]
1542
1667
async fn when_the_peer_is_a_seeder ( ) {
1543
1668
let tracker = public_tracker ( ) ;
1544
1669
1545
1670
let mut peer = seeder ( ) ;
1546
1671
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 ) ;
1548
1673
1549
1674
assert_eq ! ( announce_data. stats. complete, 1 ) ;
1550
1675
}
@@ -1555,7 +1680,7 @@ mod tests {
1555
1680
1556
1681
let mut peer = leecher ( ) ;
1557
1682
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 ) ;
1559
1684
1560
1685
assert_eq ! ( announce_data. stats. incomplete, 1 ) ;
1561
1686
}
@@ -1566,10 +1691,11 @@ mod tests {
1566
1691
1567
1692
// We have to announce with "started" event because peer does not count if peer was not previously known
1568
1693
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 ) ;
1570
1695
1571
1696
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 ) ;
1573
1699
1574
1700
assert_eq ! ( announce_data. stats. downloaded, 1 ) ;
1575
1701
}
@@ -1583,7 +1709,7 @@ mod tests {
1583
1709
use torrust_tracker_primitives:: info_hash:: InfoHash ;
1584
1710
1585
1711
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 } ;
1587
1713
1588
1714
#[ tokio:: test]
1589
1715
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 {
1609
1735
1610
1736
// Announce a "complete" peer for the torrent
1611
1737
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
+ ) ;
1613
1744
1614
1745
// Announce an "incomplete" peer for the torrent
1615
1746
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
+ ) ;
1617
1753
1618
1754
// Scrape
1619
1755
let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
@@ -1740,7 +1876,7 @@ mod tests {
1740
1876
use crate :: core:: tests:: the_tracker:: {
1741
1877
complete_peer, incomplete_peer, peer_ip, sample_info_hash, whitelisted_tracker,
1742
1878
} ;
1743
- use crate :: core:: ScrapeData ;
1879
+ use crate :: core:: { PeersWanted , ScrapeData } ;
1744
1880
1745
1881
#[ test]
1746
1882
fn it_should_be_able_to_build_a_zeroed_scrape_data_for_a_list_of_info_hashes ( ) {
@@ -1761,11 +1897,11 @@ mod tests {
1761
1897
let info_hash = "3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0" . parse :: < InfoHash > ( ) . unwrap ( ) ;
1762
1898
1763
1899
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 ) ;
1765
1901
1766
1902
// Announce twice to force non zeroed swarm metadata
1767
1903
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 ) ;
1769
1905
1770
1906
let scrape_data = tracker. scrape ( & vec ! [ info_hash] ) . await ;
1771
1907
0 commit comments