File indexing completed on 2025-01-05 04:37:24

0001 /*
0002     SPDX-FileCopyrightText: 2005 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 #include "advancedchokealgorithm.h"
0007 
0008 #include <algorithm>
0009 
0010 #include <diskio/chunkmanager.h>
0011 #include <interfaces/torrentinterface.h>
0012 #include <peer/peer.h>
0013 #include <peer/peermanager.h>
0014 #include <util/functions.h>
0015 
0016 #include <QRandomGenerator>
0017 
0018 namespace bt
0019 {
0020 const Uint32 OPT_SEL_INTERVAL = 30 * 1000; // we switch optimistic peer each 30 seconds
0021 const double NEWBIE_BONUS = 1.0;
0022 const double SNUB_PENALTY = 10.0;
0023 
0024 AdvancedChokeAlgorithm::AdvancedChokeAlgorithm()
0025     : ChokeAlgorithm()
0026 {
0027     last_opt_sel_time = 0;
0028 }
0029 
0030 AdvancedChokeAlgorithm::~AdvancedChokeAlgorithm()
0031 {
0032 }
0033 
0034 bool AdvancedChokeAlgorithm::calcACAScore(Peer::Ptr p, ChunkManager &cman, const TorrentStats &stats)
0035 {
0036     const PeerInterface::Stats &s = p->getStats();
0037     if (p->isSeeder() || s.partial_seed) {
0038         p->setACAScore(0.0);
0039         return false;
0040     }
0041 
0042     bool should_be_interested = false;
0043     // before we start calculating first check if we have piece that the peer doesn't have
0044     const BitSet &ours = cman.getBitSet();
0045     const BitSet &theirs = p->getBitSet();
0046 
0047     should_be_interested = !theirs.includesBitSet(ours);
0048 
0049     if (!should_be_interested || !p->isInterested()) {
0050         // not interseted so it doesn't make sense to unchoke it
0051         p->setACAScore(-50.0);
0052         return false;
0053     }
0054 
0055     double nb = 0.0; // newbie bonus
0056     double cp = 0.0; // choke penalty
0057     double sp = s.snubbed ? SNUB_PENALTY : 0.0; // snubbing penalty
0058     double lb = s.local ? 10.0 : 0.0; // local peers get a bonus of 10
0059     double bd = s.bytes_downloaded; // bytes downloaded
0060     double tbd = stats.session_bytes_downloaded; // total bytes downloaded
0061     double ds = s.download_rate; // current download rate
0062     double tds = stats.download_rate; // total download speed
0063 
0064     // if the peer has less than 1 MB or 0.5 % of the torrent it is a newbie
0065     if (p->percentAvailable() < 0.5 && stats.total_bytes * p->percentAvailable() < 1024 * 1024) {
0066         nb = NEWBIE_BONUS;
0067     }
0068 
0069     if (p->isChoked()) {
0070         cp = NEWBIE_BONUS; // cp cancels out newbie bonus
0071     }
0072 
0073     // NB + K * (BD/TBD) - CP - SP + L * (DS / TDS)
0074     double K = 5.0;
0075     double L = 5.0;
0076     double aca = lb + nb + (tbd > 0 ? K * (bd / tbd) : 0.0) + (tds > 0 ? L * (ds / tds) : 0.0) - cp - sp;
0077 
0078     p->setACAScore(aca);
0079     return true;
0080 }
0081 
0082 static bool ACAGreaterThan(Peer::Ptr a, Peer::Ptr b)
0083 {
0084     return a->getStats().aca_score > b->getStats().aca_score;
0085 }
0086 
0087 void AdvancedChokeAlgorithm::doChokingLeechingState(PeerManager &pman, ChunkManager &cman, const TorrentStats &stats)
0088 {
0089     QList<Peer::Ptr> ppl = pman.getPeers();
0090     for (QList<Peer::Ptr>::iterator i = ppl.begin(); i != ppl.end();) {
0091         Peer::Ptr p = *i;
0092         if (!calcACAScore(p, cman, stats)) {
0093             // choke seeders they do not want to download from us anyway
0094             p->choke();
0095             i = ppl.erase(i);
0096         } else
0097             ++i;
0098     }
0099 
0100     // sort list by ACA score
0101     std::sort(ppl.begin(), ppl.end(), ACAGreaterThan);
0102 
0103     doUnchoking(ppl, updateOptimisticPeer(pman, ppl));
0104 }
0105 
0106 void AdvancedChokeAlgorithm::doUnchoking(const QList<Peer::Ptr> &ppl, Peer::Ptr poup)
0107 {
0108     // Get the number of upload slots
0109     Uint32 num_slots = Choker::getNumUploadSlots();
0110     // Do the choking and unchoking
0111     Uint32 num_unchoked = 0;
0112     for (Peer::Ptr p : ppl) {
0113         if (!poup && num_unchoked < num_slots) {
0114             p->sendUnchoke();
0115             num_unchoked++;
0116         } else if (num_unchoked < num_slots - 1 || p == poup) {
0117             p->sendUnchoke();
0118             if (p != poup)
0119                 num_unchoked++;
0120         } else {
0121             p->choke();
0122         }
0123     }
0124 }
0125 
0126 static bool UploadRateGreaterThan(Peer::Ptr a, Peer::Ptr b)
0127 {
0128     return a->getStats().upload_rate > b->getStats().upload_rate;
0129 }
0130 
0131 void AdvancedChokeAlgorithm::doChokingSeedingState(PeerManager &pman, ChunkManager &cman, const TorrentStats &stats)
0132 {
0133     QList<Peer::Ptr> ppl = pman.getPeers();
0134     for (QList<Peer::Ptr>::iterator i = ppl.begin(); i != ppl.end();) {
0135         Peer::Ptr p = *i;
0136         if (!calcACAScore(p, cman, stats)) {
0137             // choke seeders they do not want to download from us anyway
0138             p->choke();
0139             i = ppl.erase(i);
0140         } else
0141             ++i;
0142     }
0143 
0144     std::sort(ppl.begin(), ppl.end(), UploadRateGreaterThan);
0145 
0146     doUnchoking(ppl, updateOptimisticPeer(pman, ppl));
0147 }
0148 
0149 static Uint32 FindPlannedOptimisticUnchokedPeer(const QList<Peer::Ptr> &ppl)
0150 {
0151     Uint32 num_peers = ppl.size();
0152     if (num_peers == 0)
0153         return UNDEFINED_ID;
0154 
0155     // find a random peer that is choked and interested
0156     Uint32 start = QRandomGenerator::global()->bounded(num_peers);
0157     Uint32 i = (start + 1) % num_peers;
0158     while (i != start) {
0159         Peer::Ptr p = ppl.at(i);
0160         if (p && p->isChoked() && p->isInterested() && !p->isSeeder() && ppl.contains(p))
0161             return p->getID();
0162         i = (i + 1) % num_peers;
0163     }
0164 
0165     // we do not expect to have 4 billion peers
0166     return UNDEFINED_ID;
0167 }
0168 
0169 Peer::Ptr AdvancedChokeAlgorithm::updateOptimisticPeer(PeerManager &pman, const QList<Peer::Ptr> &ppl)
0170 {
0171     // get the planned optimistic unchoked peer and change it if necessary
0172     Peer::Ptr poup = pman.findPeer(opt_unchoked_peer_id);
0173     TimeStamp now = CurrentTime();
0174     if (now - last_opt_sel_time > OPT_SEL_INTERVAL || !poup) {
0175         opt_unchoked_peer_id = FindPlannedOptimisticUnchokedPeer(ppl);
0176         last_opt_sel_time = now;
0177         poup = pman.findPeer(opt_unchoked_peer_id);
0178     }
0179     return poup;
0180 }
0181 }