root/hydranode/hncore/bt/torrent.cpp

Revision 2913, 10.6 kB (checked in by madcat, 3 years ago)

Multi-tracker support.

Line 
1 /*
2  *  Copyright (C) 2005-2006 Alo Sarv <madcat_@users.sourceforge.net>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 /**
20  * \file torrent.cpp         Implementation of Torrent class
21  */
22
23 #include <hnbase/osdep.h>
24 #include <hncore/sharedfile.h>
25 #include <hncore/partdata.h>
26 #include <hncore/bt/torrent.h>
27 #include <hncore/bt/client.h>
28 #include <hncore/bt/bittorrent.h>
29 #include <hncore/bt/tracker.h>
30 #include <hnbase/timed_callback.h>
31 #include <boost/multi_index_container.hpp>
32 #include <boost/multi_index/member.hpp>
33 #include <boost/multi_index/identity.hpp>
34 #include <boost/multi_index/ordered_index.hpp>
35 #include <boost/multi_index/sequenced_index.hpp>
36 #include <boost/lambda/construct.hpp>
37 #include <boost/lambda/bind.hpp>
38 #include <boost/spirit.hpp>
39
40 namespace Bt {
41
42 const std::string TORRENT("bt.torrent");
43
44 // Torrent class
45 // -------------
46 IMPLEMENT_EVENT_TABLE(Torrent, Torrent*, int);
47
48 Torrent::Torrent(SharedFile *file, const TorrentInfo &info)
49 : m_file(file), m_partData(), m_info(info), m_uploaded(),
50 m_downloaded() {
51         CHECK_THROW(file);
52
53         m_partData = file->getPartData();
54
55         using namespace boost::spirit;
56
57         TorrentInfo::AIter i = info.announceBegin();
58         while (i != info.announceEnd()) {
59                 std::string tmp = *i;
60                 std::string host, url;
61                 uint16_t port = 80;
62                 parse_info<> nfo = parse(
63                         tmp.data(), tmp.data() + tmp.size(),
64                         (str_p("http://") | str_p("udp://"))
65                         >> *( ( ':' >> uint_p[assign_a(port)] )
66                             | ( '/' >> *graph_p[push_back_a(url)] >> end_p)
67                             |   graph_p[push_back_a(host)]
68                             )
69                 );
70                 try {
71                         Tracker *tr = new Tracker(host, url, port, info);
72                         tr->foundPeer.connect(
73                                 boost::bind(&Torrent::createClient, this, _1)
74                         );
75                         tr->getUploaded.connect(
76                                 boost::bind(&Torrent::getUploaded, this)
77                         );
78                         tr->getDownloaded.connect(
79                                 boost::bind(&Torrent::getDownloaded, this)
80                         );
81                         tr->getPartData.connect(
82                                 boost::bind(&Torrent::getPartData, this)
83                         );
84                         m_trackers.insert(tr);
85                 } catch (std::exception &e) {
86                         logDebug(boost::format("Tracker: %s") % e.what());
87                 }
88                 ++i;
89         }
90
91         std::vector<IPV4Address> nodes = info.getNodes();
92         uint32_t foundNodes = 0;
93         for (uint32_t i = 0; i < nodes.size(); ++i) {
94                 try {
95                         createClient(nodes[i]);
96                         ++foundNodes;
97                 } catch (std::exception &e) {
98                         logError(
99                                 boost::format(
100                                         "Creating BT client from node info "
101                                         "found in .torrent: %s."
102                                 ) % e.what()
103                         );
104                 }
105         }
106         if (foundNodes) {
107                 logMsg(
108                         boost::format("Found %d nodes in .torrent file.")
109                         % foundNodes
110                 );
111         }
112         initBitfield();
113
114         SharedFile::getEventTable().addHandler(
115                 m_file, this, &Torrent::onSharedFileEvent
116         );
117         if (m_partData) {
118                 PartData::getEventTable().addHandler(
119                         m_partData, this, &Torrent::onPartDataEvent
120                 );
121                 m_partData->onVerified.connect(
122                         boost::bind(&Torrent::onChunkVerified, this, _1, _2, _3)
123                 );
124                 m_partData->getSourceCnt.connect(
125                         boost::bind(&Torrent::getPeerCount, this)
126                 );
127         }
128 }
129
130 Torrent::~Torrent() {
131         using namespace boost::lambda;
132         for_each(m_clients.begin(), m_clients.end(), bind(delete_ptr(), __1));
133         for_each(m_trackers.begin(),m_trackers.end(),bind(delete_ptr(), __1));
134 }
135
136 void Torrent::handshakeReceived(Client *c) {
137         CHECK_RET(m_clients.find(c) != m_clients.end());
138         using namespace boost::lambda;
139         if (c->getInfoHash() != getInfoHash()) {
140                 logTrace(
141                         TORRENT, boost::format(
142                                 "[%s] Client sent wrong info_hash "
143                                 "%s (expected %s)"
144                         ) % c->getAddr() % c->getInfoHash().decode()
145                         % getInfoHash().decode()
146                 );
147                 Utils::timedCallback(bind(delete_ptr(), c), 1);
148                 m_clients.erase(c);
149         } else if (m_file) {
150                 logTrace(TORRENT,
151                         boost::format("[%s] Attaching to %s")
152                         % c->getAddr() % getName()
153                 );
154                 c->setFile(m_file);
155                 c->setFile(m_partData);
156                 c->setTorrent(this);
157         } else {
158                 Utils::timedCallback(bind(delete_ptr(), c), 1);
159                 m_clients.erase(c);
160         }
161 }
162
163 void Torrent::connectionLost(Client *c) {
164         CHECK_RET(m_clients.find(c) != m_clients.end());
165         delete c;
166         m_clients.erase(c);
167 }
168
169 void Torrent::addClient(Client *c) {
170         CHECK_RET(m_clients.find(c) == m_clients.end());
171         using namespace boost::lambda;
172
173         if (!m_file) {
174                 Utils::timedCallback(bind(delete_ptr(), c), 1);
175         }
176
177         c->setFile(m_file);
178         c->setFile(m_partData);
179         c->setTorrent(this);
180         c->connectionLost.connect(
181                 boost::bind(&Torrent::connectionLost, this, _b1)
182         );
183         m_clients.insert(c);
184 }
185
186 void Torrent::addRequest(const Client::Request &r) {
187         m_sharedReqs.push_back(r.m_locked);
188 }
189
190 void Torrent::delRequest(const Client::Request &r) {
191         typedef std::deque<
192                 boost::weak_ptr< ::Detail::LockedRange>
193         >::iterator Iter;
194         for (Iter i = m_sharedReqs.begin(); i != m_sharedReqs.end(); ++i) {
195                 ::Detail::LockedRangePtr l = (*i).lock();
196                 if (!l || l == r.m_locked) {
197                         m_sharedReqs.erase(i);
198                         i = m_sharedReqs.begin();
199                 }
200                 if (!m_sharedReqs.size()) {
201                         break;
202                 }
203         }
204 }
205
206 Client::Request Torrent::getRequest(const std::vector<bool> &chunks) {
207         CHECK_THROW(m_sharedReqs.size());
208         logDebug(
209                 boost::format("There are %d shared requests.")
210                 % m_sharedReqs.size()
211         );
212
213         typedef std::deque<
214                 boost::weak_ptr< ::Detail::LockedRange>
215         >::iterator Iter;
216         for (Iter i = m_sharedReqs.begin(); i != m_sharedReqs.end(); ++i) {
217                 if (!(*i).lock()) {
218                         m_sharedReqs.erase(i);
219                         i = m_sharedReqs.begin();
220                         if (!m_sharedReqs.size()) {
221                                 break;
222                         }
223                 } else {
224                         Client::Request r((*i).lock(), this);
225                         if (!chunks.size() || chunks[r.m_index]) {
226                                 m_sharedReqs.erase(i);
227                                 addRequest(r);
228                                 return r;
229                         }
230                 }
231         }
232         throw std::runtime_error("Unable to find suitable request.");
233 }
234
235 void Torrent::clearRequests(uint32_t index) {
236         typedef std::deque<
237                 boost::weak_ptr< ::Detail::LockedRange>
238         >::iterator Iter;
239         for (Iter i = m_sharedReqs.begin(); i != m_sharedReqs.end(); ++i) {
240                 ::Detail::LockedRangePtr l = (*i).lock();
241                 if (!l || Client::Request(l, this).m_index == index) {
242                         m_sharedReqs.erase(i);
243                         i = m_sharedReqs.begin();
244                 }
245                 if (!m_sharedReqs.size()) {
246                         break;
247                 }
248         }
249 }
250
251 struct Pred {
252         bool operator()(const Client *const c1, const Client *const c2) const {
253                 int i1 = c1->amChoking() * !c1->isUploading()
254                         * c1->getDownloadSpeed();
255                 int i2 = c2->amChoking() * !c2->isUploading()
256                         * c2->getDownloadSpeed();
257                 return i1 < i2;
258         }
259 };
260
261 bool Torrent::tryUnchoke(Client *cc /* = 0 */, bool *dontChoke /* = 0 */) {
262         std::multiset<Client*, Pred> candidates;
263         std::set<Client*>::iterator itor(m_clients.begin());
264         while (itor != m_clients.end()) {
265                 candidates.insert(*itor);
266                 ++itor;
267         }
268
269         uint32_t numUploading = 0;
270         std::multiset<Client*, Pred>::iterator it(candidates.begin());
271         while (it != candidates.end()) {
272                 numUploading += (*it)->isUploading();
273                 if (numUploading > 4) {
274                         return false;
275                 }
276                 ++it;
277         }
278
279         it = candidates.begin();
280         std::vector<Client*> cand2;
281         while (it != candidates.end()) {
282                 Client *c = *it++;
283                 if (c->amChoking() && c->isInterested()) {
284                         if (c == cc && dontChoke) {
285                                 *dontChoke = true;
286                         } else {
287                                 c->sendUnchoke();
288                                 ++numUploading;
289                                 if (numUploading >= 4) {
290                                         return true;
291                                 }
292                         }
293                 } else if (c->amChoking()) {
294                         cand2.push_back(c);
295                 }
296         }
297
298         // uninterested clients - unchoke so if they become interested, they
299         // get slot instantly
300         while (cand2.size() > 1 && numUploading < 4) {
301                 uint32_t num = Utils::getRandom() % cand2.size();
302                 assert(num < cand2.size());
303                 if (cand2[num] == cc && dontChoke) {
304                         *dontChoke = true;
305                 } else {
306                         cand2[num]->sendUnchoke();
307                         ++numUploading;
308                 }
309         }
310         return true;
311 }
312
313 void Torrent::createClient(IPV4Address addr) {
314         CHECK_RET(m_file);
315
316         Client *c = new Client(addr);
317         c->setFile(m_file);
318         c->setFile(m_partData);
319         c->setTorrent(this);
320         c->setSource(m_partData);
321         c->handshakeReceived.connect(
322                 boost::bind(&Torrent::handshakeReceived, this, _1)
323         );
324         c->connectionLost.connect(
325                 boost::bind(&Torrent::connectionLost, this, _1)
326         );
327         m_clients.insert(c);
328 }
329
330 void Torrent::initBitfield() {
331         m_bitField.clear();
332         std::vector<bool> chunks;
333         if (m_partData) {
334                 chunks = m_partData->getPartStatus(m_info.getChunkSize());
335         } else {
336                 chunks = std::vector<bool>(m_info.getChunkCnt(), true);
337         }
338         std::vector<bool>::const_iterator it = chunks.begin();
339         while (it != chunks.end()) {
340                 uint8_t tmp = 0;
341                 bool exitLoop = false;
342                 for (int8_t i = 7; i >= 0; --i, ++it) {
343                         if (it == chunks.end()) {
344                                 m_bitField.push_back(tmp);
345                                 exitLoop = true;
346                                 break;
347                         } else {
348                                 tmp |= *it << i;
349                         }
350                 }
351                 if (!exitLoop) {
352                         m_bitField.push_back(tmp);
353                 }
354         }
355
356         // self-check, ensures bitfield is properly set up as per BT spec
357 #ifndef NDEBUG
358         uint32_t trueBits = 0;
359         std::vector<bool> tmp;
360         for (uint32_t i = 0; i < m_bitField.size(); ++i) {
361                 std::bitset<8> b((unsigned)m_bitField[i]);
362                 for (int8_t j = 7; j >= 0; --j) {
363                         tmp.push_back(b[j]);
364                         if (b[j]) {
365                                 ++trueBits;
366                         }
367                         if (tmp.size() == m_info.getChunkCnt()) {
368                                 break;
369                         }
370                 }
371                 if (tmp.size() == m_info.getChunkCnt()) {
372                         break;
373                 }
374         }
375         CHECK_FAIL(tmp.size() == m_info.getChunkCnt());
376 #endif
377 }
378
379 void Torrent::onChunkVerified(
380         PartData *file, uint64_t chunkSize, uint64_t chunk
381 ) {
382         CHECK_RET(file == m_partData);
383
384         if (chunkSize != m_info.getChunkSize()) {
385                 return;
386         }
387         uint32_t num = chunk / 8;
388         m_bitField.at(num) |= 1 << 7 - chunk % 8;
389
390         // self-check, ensures we set the right bit above
391 #ifndef NDEBUG
392         std::vector<bool> tmp;
393         for (uint32_t i = 0; i < m_bitField.size(); ++i) {
394                 std::bitset<8> b((unsigned)m_bitField[i]);
395                 for (int8_t j = 7; j >= 0; --j) {
396                         tmp.push_back(b[j]);
397                 }
398         }
399         assert(tmp[chunk]);
400 #endif
401
402         clearRequests(chunk);
403 }
404
405 void Torrent::onPartDataEvent(PartData *file, int evt) {
406         if (evt == PD_DESTROY) {
407                 m_partData = 0;
408         }
409 }
410
411 void Torrent::onSharedFileEvent(SharedFile *file, int evt) {
412         using namespace boost::lambda;
413         if (!m_file) {
414                 // if we get events from file after SF_DESTROY, ignore
415                 return;
416         }
417         assert(file == m_file);
418         if (evt == SF_DESTROY) {
419                 m_file = 0;
420                 getEventTable().postEvent(this, EVT_DESTROY);
421                 for_each(
422                         m_clients.begin(), m_clients.end(),
423                         bind(delete_ptr(), __1)
424                 );
425                 m_clients.clear();
426         } else if (evt == EVT_CHILDDESTROYED) {
427                 initBitfield(); // easier to rebuild completely
428         }
429 }
430
431 void Torrent::addUploaded(uint32_t amount) {
432         m_uploaded += amount;
433         if (m_file) {
434                 m_file->addUploaded(amount);
435         }
436 }
437
438 } // end namespace Bt
Note: See TracBrowser for help on using the browser.