1 /* 2 3 Copyright (c) 2007-2018, Arvid Norberg 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions 8 are met: 9 10 * Redistributions of source code must retain the above copyright 11 notice, this list of conditions and the following disclaimer. 12 * Redistributions in binary form must reproduce the above copyright 13 notice, this list of conditions and the following disclaimer in 14 the documentation and/or other materials provided with the distribution. 15 * Neither the name of the author nor the names of its 16 contributors may be used to endorse or promote products derived 17 from this software without specific prior written permission. 18 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 POSSIBILITY OF SUCH DAMAGE. 30 31 */ 32 33 #ifndef TORRENT_DISABLE_EXTENSIONS 34 35 #include <functional> 36 #include <vector> 37 #include <utility> 38 #include <numeric> 39 #include <cstdio> 40 41 #include "libtorrent/peer_connection.hpp" 42 #include "libtorrent/bt_peer_connection.hpp" 43 #include "libtorrent/peer_connection_handle.hpp" 44 #include "libtorrent/bencode.hpp" 45 #include "libtorrent/torrent.hpp" 46 #include "libtorrent/torrent_handle.hpp" 47 #include "libtorrent/extensions.hpp" 48 #include "libtorrent/extensions/ut_metadata.hpp" 49 #include "libtorrent/alert_types.hpp" 50 #include "libtorrent/random.hpp" 51 #include "libtorrent/io.hpp" 52 #include "libtorrent/performance_counters.hpp" // for counters 53 #include "libtorrent/aux_/time.hpp" 54 55 #if TORRENT_USE_ASSERTS 56 #include "libtorrent/hasher.hpp" 57 #endif 58 59 namespace libtorrent {namespace { 60 61 enum 62 { 63 // this is the max number of bytes we'll 64 // queue up in the send buffer. If we exceed this, 65 // we'll wait another second before checking 66 // the send buffer size again. So, this may limit 67 // the rate at which we can server metadata to 68 // 160 kiB/s 69 send_buffer_limit = 0x4000 * 10, 70 71 // this is the max number of requests we'll queue 72 // up. If we get more requests tha this, we'll 73 // start rejecting them, claiming we don't have 74 // metadata. If the torrent is greater than 16 MiB, 75 // we may hit this case (and the client requesting 76 // doesn't throttle its requests) 77 max_incoming_requests = 1024, 78 79 metadata_req = 0, 80 metadata_piece = 1, 81 metadata_dont_have = 2 82 }; 83 div_round_up(int numerator,int denominator)84 int div_round_up(int numerator, int denominator) 85 { 86 return (numerator + denominator - 1) / denominator; 87 } 88 89 struct ut_metadata_peer_plugin; 90 91 struct ut_metadata_plugin final 92 : torrent_plugin 93 { ut_metadata_pluginlibtorrent::__anonb051ff310111::ut_metadata_plugin94 explicit ut_metadata_plugin(torrent& t) : m_torrent(t) 95 { 96 // initialize m_metadata_size 97 if (m_torrent.valid_metadata()) 98 metadata(); 99 } 100 on_files_checkedlibtorrent::__anonb051ff310111::ut_metadata_plugin101 void on_files_checked() override 102 { 103 // TODO: 2 if we were to initialize m_metadata_size lazily instead, 104 // we would probably be more efficient 105 // initialize m_metadata_size 106 metadata(); 107 } 108 109 std::shared_ptr<peer_plugin> new_connection( 110 peer_connection_handle const& pc) override; 111 get_metadata_sizelibtorrent::__anonb051ff310111::ut_metadata_plugin112 int get_metadata_size() const 113 { 114 TORRENT_ASSERT(m_metadata_size > 0); 115 return m_metadata_size; 116 } 117 metadatalibtorrent::__anonb051ff310111::ut_metadata_plugin118 span<char const> metadata() const 119 { 120 TORRENT_ASSERT(m_torrent.valid_metadata()); 121 if (!m_metadata) 122 { 123 m_metadata = m_torrent.torrent_file().metadata(); 124 m_metadata_size = m_torrent.torrent_file().metadata_size(); 125 TORRENT_ASSERT(hasher(m_metadata.get(), m_metadata_size).final() 126 == m_torrent.torrent_file().info_hash()); 127 } 128 return {m_metadata.get(), m_metadata_size}; 129 } 130 131 bool received_metadata(ut_metadata_peer_plugin& source 132 , span<char const> buf, int piece, int total_size); 133 134 // returns a piece of the metadata that 135 // we should request. 136 // returns -1 if we should hold off the request 137 int metadata_request(bool has_metadata); 138 on_piece_passlibtorrent::__anonb051ff310111::ut_metadata_plugin139 void on_piece_pass(piece_index_t) override 140 { 141 // if we became a seed, copy the metadata from 142 // the torrent before it is deallocated 143 if (m_torrent.is_seed()) 144 metadata(); 145 } 146 metadata_sizelibtorrent::__anonb051ff310111::ut_metadata_plugin147 void metadata_size(int const size) 148 { 149 if (m_metadata_size > 0 || size <= 0 || size > 4 * 1024 * 1024) return; 150 m_metadata_size = size; 151 m_metadata.reset(new char[std::size_t(size)]); 152 m_requested_metadata.resize(div_round_up(size, 16 * 1024)); 153 } 154 155 // explicitly disallow assignment, to silence msvc warning 156 ut_metadata_plugin& operator=(ut_metadata_plugin const&) = delete; 157 158 private: 159 torrent& m_torrent; 160 161 // this buffer is filled with the info-section of 162 // the metadata file while downloading it from 163 // peers, and while sending it. 164 // it is mutable because it's generated lazily 165 mutable boost::shared_array<char> m_metadata; 166 167 mutable int m_metadata_size = 0; 168 169 struct metadata_piece 170 { metadata_piecelibtorrent::__anonb051ff310111::ut_metadata_plugin::metadata_piece171 metadata_piece(): num_requests(0), last_request(min_time()) {} 172 int num_requests; 173 time_point last_request; 174 std::weak_ptr<ut_metadata_peer_plugin> source; operator <libtorrent::__anonb051ff310111::ut_metadata_plugin::metadata_piece175 bool operator<(metadata_piece const& rhs) const 176 { return num_requests < rhs.num_requests; } 177 }; 178 179 // this vector keeps track of how many times each metadata 180 // block has been requested and who we ended up getting it from 181 // std::numeric_limits<int>::max() means we have the piece 182 aux::vector<metadata_piece> m_requested_metadata; 183 }; 184 185 186 struct ut_metadata_peer_plugin final 187 : peer_plugin, std::enable_shared_from_this<ut_metadata_peer_plugin> 188 { 189 friend struct ut_metadata_plugin; 190 ut_metadata_peer_pluginlibtorrent::__anonb051ff310111::ut_metadata_peer_plugin191 ut_metadata_peer_plugin(torrent& t, bt_peer_connection& pc 192 , ut_metadata_plugin& tp) 193 : m_message_index(0) 194 , m_request_limit(min_time()) 195 , m_torrent(t) 196 , m_pc(pc) 197 , m_tp(tp) 198 {} 199 200 // can add entries to the extension handshake add_handshakelibtorrent::__anonb051ff310111::ut_metadata_peer_plugin201 void add_handshake(entry& h) override 202 { 203 entry& messages = h["m"]; 204 messages["ut_metadata"] = 2; 205 if (m_torrent.valid_metadata()) 206 h["metadata_size"] = m_tp.get_metadata_size(); 207 } 208 209 // called when the extension handshake from the other end is received on_extension_handshakelibtorrent::__anonb051ff310111::ut_metadata_peer_plugin210 bool on_extension_handshake(bdecode_node const& h) override 211 { 212 m_message_index = 0; 213 if (h.type() != bdecode_node::dict_t) return false; 214 bdecode_node const messages = h.dict_find_dict("m"); 215 if (!messages) return false; 216 217 int index = int(messages.dict_find_int_value("ut_metadata", -1)); 218 if (index == -1) return false; 219 m_message_index = index; 220 221 int metadata_size = int(h.dict_find_int_value("metadata_size")); 222 if (metadata_size > 0) 223 m_tp.metadata_size(metadata_size); 224 else 225 m_pc.set_has_metadata(false); 226 227 maybe_send_request(); 228 return true; 229 } 230 write_metadata_packetlibtorrent::__anonb051ff310111::ut_metadata_peer_plugin231 void write_metadata_packet(int const type, int const piece) 232 { 233 TORRENT_ASSERT(type >= 0 && type <= 2); 234 TORRENT_ASSERT(!m_pc.associated_torrent().expired()); 235 236 #ifndef TORRENT_DISABLE_LOGGING 237 static char const* names[] = {"request", "data", "dont-have"}; 238 char const* n = ""; 239 if (type >= 0 && type < 3) n = names[type]; 240 m_pc.peer_log(peer_log_alert::outgoing_message, "UT_METADATA" 241 , "type: %d (%s) piece: %d", type, n, piece); 242 #endif 243 244 // abort if the peer doesn't support the metadata extension 245 if (m_message_index == 0) return; 246 247 entry e; 248 e["msg_type"] = type; 249 e["piece"] = piece; 250 251 char const* metadata = nullptr; 252 int metadata_piece_size = 0; 253 254 if (m_torrent.valid_metadata()) 255 e["total_size"] = m_tp.get_metadata_size(); 256 257 if (type == 1) 258 { 259 TORRENT_ASSERT(piece >= 0 && piece < (m_tp.get_metadata_size() + 16 * 1024 - 1) / (16 * 1024)); 260 TORRENT_ASSERT(m_pc.associated_torrent().lock()->valid_metadata()); 261 TORRENT_ASSERT(m_torrent.valid_metadata()); 262 263 int const offset = piece * 16 * 1024; 264 metadata = m_tp.metadata().data() + offset; 265 metadata_piece_size = std::min( 266 m_tp.get_metadata_size() - offset, 16 * 1024); 267 TORRENT_ASSERT(metadata_piece_size > 0); 268 TORRENT_ASSERT(offset >= 0); 269 TORRENT_ASSERT(offset + metadata_piece_size <= m_tp.get_metadata_size()); 270 } 271 272 // TODO: 3 use the aux::write_* functions and the span here instead, it 273 // will fit better with send_buffer() 274 char msg[200]; 275 char* header = msg; 276 char* p = &msg[6]; 277 int const len = bencode(p, e); 278 int const total_size = 2 + len + metadata_piece_size; 279 namespace io = detail; 280 io::write_uint32(total_size, header); 281 io::write_uint8(bt_peer_connection::msg_extended, header); 282 io::write_uint8(m_message_index, header); 283 284 m_pc.send_buffer({msg, len + 6}); 285 // TODO: we really need to increment the refcounter on the torrent 286 // while this buffer is still in the peer's send buffer 287 if (metadata_piece_size) 288 { 289 m_pc.append_const_send_buffer( 290 span<char>(const_cast<char*>(metadata), metadata_piece_size), metadata_piece_size); 291 } 292 293 m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_extended); 294 m_pc.stats_counters().inc_stats_counter(counters::num_outgoing_metadata); 295 } 296 on_extendedlibtorrent::__anonb051ff310111::ut_metadata_peer_plugin297 bool on_extended(int const length 298 , int const extended_msg, span<char const> body) override 299 { 300 if (extended_msg != 2) return false; 301 if (m_message_index == 0) return false; 302 303 if (length > 17 * 1024) 304 { 305 #ifndef TORRENT_DISABLE_LOGGING 306 m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" 307 , "packet too big %d", length); 308 #endif 309 m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); 310 return true; 311 } 312 313 if (!m_pc.packet_finished()) return true; 314 315 error_code ec; 316 bdecode_node msg = bdecode(body, ec); 317 if (msg.type() != bdecode_node::dict_t) 318 { 319 #ifndef TORRENT_DISABLE_LOGGING 320 m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" 321 , "not a dictionary"); 322 #endif 323 m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); 324 return true; 325 } 326 327 bdecode_node const& type_ent = msg.dict_find_int("msg_type"); 328 bdecode_node const& piece_ent = msg.dict_find_int("piece"); 329 if (!type_ent || !piece_ent) 330 { 331 #ifndef TORRENT_DISABLE_LOGGING 332 m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" 333 , "missing or invalid keys"); 334 #endif 335 m_pc.disconnect(errors::invalid_metadata_message, operation_t::bittorrent, peer_connection_interface::peer_error); 336 return true; 337 } 338 // TODO: make this an enum class 339 auto const type = static_cast<int>(type_ent.int_value()); 340 auto const piece = static_cast<int>(piece_ent.int_value()); 341 342 #ifndef TORRENT_DISABLE_LOGGING 343 m_pc.peer_log(peer_log_alert::incoming_message, "UT_METADATA" 344 , "type: %d piece: %d", type, piece); 345 #endif 346 347 switch (type) 348 { 349 case metadata_req: 350 { 351 if (!m_torrent.valid_metadata() 352 || piece < 0 || piece >= (m_tp.get_metadata_size() + 16 * 1024 - 1) / (16 * 1024)) 353 { 354 #ifndef TORRENT_DISABLE_LOGGING 355 if (m_pc.should_log(peer_log_alert::info)) 356 { 357 m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 358 , "have: %d invalid piece %d metadata size: %d" 359 , int(m_torrent.valid_metadata()), piece 360 , m_torrent.valid_metadata() 361 ? m_tp.get_metadata_size() : 0); 362 } 363 #endif 364 write_metadata_packet(metadata_dont_have, piece); 365 return true; 366 } 367 if (m_pc.send_buffer_size() < send_buffer_limit) 368 write_metadata_packet(metadata_piece, piece); 369 else if (m_incoming_requests.size() < max_incoming_requests) 370 m_incoming_requests.push_back(piece); 371 else 372 write_metadata_packet(metadata_dont_have, piece); 373 } 374 break; 375 case metadata_piece: 376 { 377 auto const i = std::find(m_sent_requests.begin() 378 , m_sent_requests.end(), piece); 379 380 // unwanted piece? 381 if (i == m_sent_requests.end()) 382 { 383 #ifndef TORRENT_DISABLE_LOGGING 384 m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 385 , "UNWANTED / TIMED OUT"); 386 #endif 387 return true; 388 } 389 390 m_sent_requests.erase(i); 391 auto const len = msg.data_section().size(); 392 auto const total_size = msg.dict_find_int_value("total_size", 0); 393 m_tp.received_metadata(*this, body.subspan(len), piece, static_cast<int>(total_size)); 394 maybe_send_request(); 395 } 396 break; 397 case metadata_dont_have: 398 { 399 m_request_limit = std::max(aux::time_now() + minutes(1), m_request_limit); 400 auto const i = std::find(m_sent_requests.begin() 401 , m_sent_requests.end(), piece); 402 // unwanted piece? 403 if (i == m_sent_requests.end()) return true; 404 m_sent_requests.erase(i); 405 } 406 break; 407 default: 408 // unknown message, ignore 409 break; 410 } 411 412 m_pc.stats_counters().inc_stats_counter(counters::num_incoming_metadata); 413 414 return true; 415 } 416 ticklibtorrent::__anonb051ff310111::ut_metadata_peer_plugin417 void tick() override 418 { 419 maybe_send_request(); 420 while (!m_incoming_requests.empty() 421 && m_pc.send_buffer_size() < send_buffer_limit) 422 { 423 int const piece = m_incoming_requests.front(); 424 m_incoming_requests.erase(m_incoming_requests.begin()); 425 write_metadata_packet(metadata_piece, piece); 426 } 427 } 428 maybe_send_requestlibtorrent::__anonb051ff310111::ut_metadata_peer_plugin429 void maybe_send_request() 430 { 431 if (m_pc.is_disconnecting()) return; 432 433 // if we don't have any metadata, and this peer 434 // supports the request metadata extension 435 // and we aren't currently waiting for a request 436 // reply. Then, send a request for some metadata. 437 if (!m_torrent.valid_metadata() 438 && m_message_index != 0 439 && m_sent_requests.size() < 2 440 && has_metadata()) 441 { 442 int const piece = m_tp.metadata_request(m_pc.has_metadata()); 443 if (piece == -1) return; 444 445 m_sent_requests.push_back(piece); 446 write_metadata_packet(metadata_req, piece); 447 } 448 } 449 has_metadatalibtorrent::__anonb051ff310111::ut_metadata_peer_plugin450 bool has_metadata() const 451 { 452 return m_pc.has_metadata() || (aux::time_now() > m_request_limit); 453 } 454 failed_hash_checklibtorrent::__anonb051ff310111::ut_metadata_peer_plugin455 void failed_hash_check(time_point const& now) 456 { 457 m_request_limit = now + seconds(20 + random(50)); 458 } 459 460 // explicitly disallow assignment, to silence msvc warning 461 ut_metadata_peer_plugin& operator=(ut_metadata_peer_plugin const&) = delete; 462 463 private: 464 465 // this is the message index the remote peer uses 466 // for metadata extension messages. 467 int m_message_index; 468 469 // this is set to the next time we can request pieces 470 // again. It is updated every time we get a 471 // "I don't have metadata" message, but also when 472 // we receive metadata that fails the infohash check 473 time_point m_request_limit; 474 475 // request queues 476 std::vector<int> m_sent_requests; 477 std::vector<int> m_incoming_requests; 478 479 torrent& m_torrent; 480 bt_peer_connection& m_pc; 481 ut_metadata_plugin& m_tp; 482 }; 483 new_connection(peer_connection_handle const & pc)484 std::shared_ptr<peer_plugin> ut_metadata_plugin::new_connection( 485 peer_connection_handle const& pc) 486 { 487 if (pc.type() != connection_type::bittorrent) return {}; 488 489 bt_peer_connection* c = static_cast<bt_peer_connection*>(pc.native_handle().get()); 490 return std::make_shared<ut_metadata_peer_plugin>(m_torrent, *c, *this); 491 } 492 493 // has_metadata is false if the peer making the request has not announced 494 // that it has metadata. In this case, it shouldn't prevent other peers 495 // from requesting this block by setting a timeout on it. metadata_request(bool const has_metadata)496 int ut_metadata_plugin::metadata_request(bool const has_metadata) 497 { 498 auto i = std::min_element( 499 m_requested_metadata.begin(), m_requested_metadata.end()); 500 501 if (m_requested_metadata.empty()) 502 { 503 // if we don't know how many pieces there are 504 // just ask for piece 0 505 m_requested_metadata.resize(1); 506 i = m_requested_metadata.begin(); 507 } 508 509 int const piece = int(i - m_requested_metadata.begin()); 510 511 // don't request the same block more than once every 3 seconds 512 time_point const now = aux::time_now(); 513 if (m_requested_metadata[piece].last_request != min_time() 514 && total_seconds(now - m_requested_metadata[piece].last_request) < 3) 515 return -1; 516 517 ++m_requested_metadata[piece].num_requests; 518 519 // only set the timeout on this block, only if the peer 520 // has metadata. This is to prevent peers with no metadata 521 // to starve out sending requests to peers with metadata 522 if (has_metadata) 523 m_requested_metadata[piece].last_request = now; 524 525 return piece; 526 } 527 received_metadata(ut_metadata_peer_plugin & source,span<char const> buf,int const piece,int const total_size)528 bool ut_metadata_plugin::received_metadata(ut_metadata_peer_plugin& source 529 , span<char const> buf, int const piece, int const total_size) 530 { 531 if (m_torrent.valid_metadata()) 532 { 533 #ifndef TORRENT_DISABLE_LOGGING 534 source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 535 , "already have metadata"); 536 #endif 537 m_torrent.add_redundant_bytes(static_cast<int>(buf.size()), waste_reason::piece_unknown); 538 return false; 539 } 540 541 if (!m_metadata) 542 { 543 // verify the total_size 544 if (total_size <= 0 || total_size > m_torrent.session().settings().get_int(settings_pack::max_metadata_size)) 545 { 546 #ifndef TORRENT_DISABLE_LOGGING 547 source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 548 , "metadata size too big: %d", total_size); 549 #endif 550 // #error post alert 551 return false; 552 } 553 554 m_metadata.reset(new char[std::size_t(total_size)]); 555 m_requested_metadata.resize(div_round_up(total_size, 16 * 1024)); 556 m_metadata_size = total_size; 557 } 558 559 if (piece < 0 || piece >= m_requested_metadata.end_index()) 560 { 561 #ifndef TORRENT_DISABLE_LOGGING 562 source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 563 , "piece: %d INVALID", piece); 564 #endif 565 return false; 566 } 567 568 if (total_size != m_metadata_size) 569 { 570 #ifndef TORRENT_DISABLE_LOGGING 571 source.m_pc.peer_log(peer_log_alert::info, "UT_METADATA" 572 , "total_size: %d INCONSISTENT WITH: %d" 573 , total_size, m_metadata_size); 574 #endif 575 // they disagree about the size! 576 return false; 577 } 578 579 if (piece * 16 * 1024 + buf.size() > m_metadata_size) 580 { 581 // this piece is invalid 582 return false; 583 } 584 585 std::memcpy(&m_metadata[piece * 16 * 1024], buf.data(), aux::numeric_cast<std::size_t>(buf.size())); 586 // mark this piece has 'have' 587 m_requested_metadata[piece].num_requests = std::numeric_limits<int>::max(); 588 m_requested_metadata[piece].source = source.shared_from_this(); 589 590 bool have_all = std::all_of(m_requested_metadata.begin(), m_requested_metadata.end() 591 , [](metadata_piece const& mp) { return mp.num_requests == std::numeric_limits<int>::max(); }); 592 593 if (!have_all) return false; 594 595 if (!m_torrent.set_metadata({m_metadata.get(), m_metadata_size})) 596 { 597 if (!m_torrent.valid_metadata()) 598 { 599 time_point const now = aux::time_now(); 600 // any peer that we downloaded metadata from gets a random time 601 // penalty, from 5 to 30 seconds or so. During this time we don't 602 // make any metadata requests from those peers (to mix it up a bit 603 // of which peers we use) 604 // if we only have one block, and thus requested it from a single 605 // peer, we bump up the retry time a lot more to try other peers 606 bool single_peer = m_requested_metadata.size() == 1; 607 for (auto& mp : m_requested_metadata) 608 { 609 mp.num_requests = 0; 610 auto peer = mp.source.lock(); 611 if (!peer) continue; 612 613 peer->failed_hash_check(single_peer ? now + minutes(5) : now); 614 } 615 } 616 return false; 617 } 618 619 // free our copy of the metadata and get a reference 620 // to the torrent's copy instead. No need to keep two 621 // identical copies around 622 m_metadata.reset(); 623 metadata(); 624 625 // clear the storage for the bitfield 626 m_requested_metadata.clear(); 627 m_requested_metadata.shrink_to_fit(); 628 629 return true; 630 } 631 632 } } 633 634 namespace libtorrent { 635 create_ut_metadata_plugin(torrent_handle const & th,void *)636 std::shared_ptr<torrent_plugin> create_ut_metadata_plugin(torrent_handle const& th, void*) 637 { 638 torrent* t = th.native_handle().get(); 639 // don't add this extension if the torrent is private 640 if (t->valid_metadata() && t->torrent_file().priv()) return {}; 641 return std::make_shared<ut_metadata_plugin>(*t); 642 } 643 } 644 645 #endif 646